mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '2.0'
This commit is contained in:
commit
9f470cec6c
10
CHANGES
10
CHANGES
@ -39,6 +39,11 @@ Features added
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* py domain: duplicated warning does not point the location of source code
|
||||
* #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
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
@ -60,6 +65,11 @@ Features added
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #6442: LaTeX: admonitions of :rst:dir:`note` type can get separated from
|
||||
immediately preceding section title by pagebreak
|
||||
* #6448: autodoc: crashed when autodocumenting classes with ``__slots__ = None``
|
||||
* #6452: autosummary: crashed when generating document of properties
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
|
@ -159,6 +159,9 @@ class CheckExternalLinksBuilder(Builder):
|
||||
if err.response.status_code == 401:
|
||||
# We'll take "Unauthorized" as working.
|
||||
return 'working', ' - unauthorized', 0
|
||||
elif err.response.status_code == 503:
|
||||
# We'll take "Service Unavailable" as ignored.
|
||||
return 'ignored', str(err), 0
|
||||
else:
|
||||
return 'broken', str(err), 0
|
||||
except Exception as err:
|
||||
|
@ -328,7 +328,8 @@ class PyObject(ObjectDescription):
|
||||
self.state.document.note_explicit_target(signode)
|
||||
|
||||
domain = cast(PythonDomain, self.env.get_domain('py'))
|
||||
domain.note_object(fullname, self.objtype)
|
||||
domain.note_object(fullname, self.objtype,
|
||||
location=(self.env.docname, self.lineno))
|
||||
|
||||
indextext = self.get_index_text(modname, name_cls)
|
||||
if indextext:
|
||||
@ -743,7 +744,7 @@ class PyModule(SphinxDirective):
|
||||
self.options.get('synopsis', ''),
|
||||
self.options.get('platform', ''),
|
||||
'deprecated' in self.options)
|
||||
domain.note_object(modname, 'module')
|
||||
domain.note_object(modname, 'module', location=(self.env.docname, self.lineno))
|
||||
|
||||
targetnode = nodes.target('', '', ids=['module-' + modname],
|
||||
ismod=True)
|
||||
|
@ -137,7 +137,7 @@ class DownloadFileCollector(EnvironmentCollector):
|
||||
logger.warning(__('download file not readable: %s') % filename,
|
||||
location=node, type='download', subtype='not_readable')
|
||||
continue
|
||||
node['filename'] = app.env.dlfiles.add_file(app.env.docname, filename)
|
||||
node['filename'] = app.env.dlfiles.add_file(app.env.docname, rel_filename)
|
||||
|
||||
|
||||
def setup(app):
|
||||
|
@ -221,7 +221,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
get_members(obj, {'attribute', 'property'})
|
||||
|
||||
parts = name.split('.')
|
||||
if doc.objtype in ('method', 'attribute'):
|
||||
if doc.objtype in ('method', 'attribute', 'property'):
|
||||
mod_name = '.'.join(parts[:-2])
|
||||
cls_name = parts[-2]
|
||||
obj_name = '.'.join(parts[-2:])
|
||||
|
@ -40,8 +40,8 @@ Submodules
|
||||
{{- [submodule, "module"] | join(" ") | e | heading(2) }}
|
||||
{% endif %}
|
||||
{{ automodule(submodule, automodule_options) }}
|
||||
{%- endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{%- endif %}
|
||||
{% endif %}
|
||||
|
||||
{%- if not modulefirst and not is_namespace %}
|
||||
|
@ -6,7 +6,7 @@
|
||||
%
|
||||
|
||||
\NeedsTeXFormat{LaTeX2e}[1995/12/01]
|
||||
\ProvidesPackage{sphinx}[2019/01/12 v1.8.4 LaTeX package (Sphinx markup)]
|
||||
\ProvidesPackage{sphinx}[2019/06/04 v2.1.1 LaTeX package (Sphinx markup)]
|
||||
|
||||
% provides \ltx@ifundefined
|
||||
% (many packages load ltxcmds: graphicx does for pdftex and lualatex but
|
||||
@ -1444,7 +1444,7 @@
|
||||
% Some are quite plain
|
||||
% the spx@notice@bordercolor etc are set in the sphinxadmonition environment
|
||||
\newenvironment{sphinxlightbox}{%
|
||||
\par\allowbreak
|
||||
\par
|
||||
\noindent{\color{spx@notice@bordercolor}%
|
||||
\rule{\linewidth}{\spx@notice@border}}\par\nobreak
|
||||
{\parskip\z@skip\noindent}%
|
||||
|
@ -13,6 +13,11 @@
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
html {
|
||||
/* CSS hack for macOS's scrollbar (see #1125) */
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: {{ theme_bodyfont }};
|
||||
font-size: 100%;
|
||||
|
@ -24,6 +24,9 @@ from datetime import datetime
|
||||
from hashlib import md5
|
||||
from os import path
|
||||
from time import mktime, strptime
|
||||
from typing import (
|
||||
Any, Callable, Dict, IO, Iterable, Iterator, List, Pattern, Set, Tuple, Type
|
||||
)
|
||||
from urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
@ -31,6 +34,7 @@ from sphinx.errors import PycodeError, SphinxParallelError, ExtensionError
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.console import strip_colors, colorize, bold, term_width_line # type: ignore
|
||||
from sphinx.util.typing import PathMatcher
|
||||
from sphinx.util import smartypants # noqa
|
||||
|
||||
# import other utilities; partly for backwards compatibility, so don't
|
||||
@ -43,10 +47,11 @@ from sphinx.util.nodes import ( # noqa
|
||||
caption_ref_re)
|
||||
from sphinx.util.matching import patfilter # noqa
|
||||
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Dict, IO, Iterable, Iterator, List, Pattern, Set, Tuple, Type, Union # NOQA
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.builders import Builder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -57,21 +62,19 @@ url_re = re.compile(r'(?P<schema>.+)://.*') # type: Pattern
|
||||
|
||||
# High-level utility functions.
|
||||
|
||||
def docname_join(basedocname, docname):
|
||||
# type: (str, str) -> str
|
||||
def docname_join(basedocname: str, docname: str) -> str:
|
||||
return posixpath.normpath(
|
||||
posixpath.join('/' + basedocname, '..', docname))[1:]
|
||||
|
||||
|
||||
def path_stabilize(filepath):
|
||||
# type: (str) -> str
|
||||
def path_stabilize(filepath: str) -> str:
|
||||
"normalize path separater and unicode string"
|
||||
newpath = filepath.replace(os.path.sep, SEP)
|
||||
return unicodedata.normalize('NFC', newpath)
|
||||
|
||||
|
||||
def get_matching_files(dirname, exclude_matchers=()):
|
||||
# type: (str, Tuple[Callable[[str], bool], ...]) -> Iterable[str]
|
||||
def get_matching_files(dirname: str,
|
||||
exclude_matchers: Tuple[PathMatcher, ...] = ()) -> Iterable[str]: # NOQA
|
||||
"""Get all file names in a directory, recursively.
|
||||
|
||||
Exclude files and dirs matching some matcher in *exclude_matchers*.
|
||||
@ -97,8 +100,8 @@ def get_matching_files(dirname, exclude_matchers=()):
|
||||
yield filename
|
||||
|
||||
|
||||
def get_matching_docs(dirname, suffixes, exclude_matchers=()):
|
||||
# type: (str, List[str], Tuple[Callable[[str], bool], ...]) -> Iterable[str] # NOQA
|
||||
def get_matching_docs(dirname: str, suffixes: List[str],
|
||||
exclude_matchers: Tuple[PathMatcher, ...] = ()) -> Iterable[str]:
|
||||
"""Get all file names (without suffixes) matching a suffix in a directory,
|
||||
recursively.
|
||||
|
||||
@ -120,12 +123,10 @@ class FilenameUniqDict(dict):
|
||||
interpreted as filenames, and keeps track of a set of docnames they
|
||||
appear in. Used for images and downloadable files in the environment.
|
||||
"""
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
def __init__(self) -> None:
|
||||
self._existing = set() # type: Set[str]
|
||||
|
||||
def add_file(self, docname, newfile):
|
||||
# type: (str, str) -> str
|
||||
def add_file(self, docname: str, newfile: str) -> str:
|
||||
if newfile in self:
|
||||
self[newfile][0].add(docname)
|
||||
return self[newfile][1]
|
||||
@ -139,26 +140,22 @@ class FilenameUniqDict(dict):
|
||||
self._existing.add(uniquename)
|
||||
return uniquename
|
||||
|
||||
def purge_doc(self, docname):
|
||||
# type: (str) -> None
|
||||
def purge_doc(self, docname: str) -> None:
|
||||
for filename, (docs, unique) in list(self.items()):
|
||||
docs.discard(docname)
|
||||
if not docs:
|
||||
del self[filename]
|
||||
self._existing.discard(unique)
|
||||
|
||||
def merge_other(self, docnames, other):
|
||||
# type: (Set[str], Dict[str, Tuple[Set[str], Any]]) -> None
|
||||
def merge_other(self, docnames: Set[str], other: Dict[str, Tuple[Set[str], Any]]) -> None:
|
||||
for filename, (docs, unique) in other.items():
|
||||
for doc in docs & set(docnames):
|
||||
self.add_file(doc, filename)
|
||||
|
||||
def __getstate__(self):
|
||||
# type: () -> Set[str]
|
||||
def __getstate__(self) -> Set[str]:
|
||||
return self._existing
|
||||
|
||||
def __setstate__(self, state):
|
||||
# type: (Set[str]) -> None
|
||||
def __setstate__(self, state: Set[str]) -> None:
|
||||
self._existing = state
|
||||
|
||||
|
||||
@ -169,8 +166,7 @@ class DownloadFiles(dict):
|
||||
Hence don't hack this directly.
|
||||
"""
|
||||
|
||||
def add_file(self, docname, filename):
|
||||
# type: (str, str) -> None
|
||||
def add_file(self, docname: str, filename: str) -> None:
|
||||
if filename not in self:
|
||||
digest = md5(filename.encode()).hexdigest()
|
||||
dest = '%s/%s' % (digest, os.path.basename(filename))
|
||||
@ -179,15 +175,13 @@ class DownloadFiles(dict):
|
||||
self[filename][0].add(docname)
|
||||
return self[filename][1]
|
||||
|
||||
def purge_doc(self, docname):
|
||||
# type: (str) -> None
|
||||
def purge_doc(self, docname: str) -> None:
|
||||
for filename, (docs, dest) in list(self.items()):
|
||||
docs.discard(docname)
|
||||
if not docs:
|
||||
del self[filename]
|
||||
|
||||
def merge_other(self, docnames, other):
|
||||
# type: (Set[str], Dict[str, Tuple[Set[str], Any]]) -> None
|
||||
def merge_other(self, docnames: Set[str], other: Dict[str, Tuple[Set[str], Any]]) -> None:
|
||||
for filename, (docs, dest) in other.items():
|
||||
for docname in docs & set(docnames):
|
||||
self.add_file(docname, filename)
|
||||
@ -204,8 +198,7 @@ _DEBUG_HEADER = '''\
|
||||
'''
|
||||
|
||||
|
||||
def save_traceback(app):
|
||||
# type: (Any) -> str
|
||||
def save_traceback(app: "Sphinx") -> str:
|
||||
"""Save the current exception's traceback in a temporary file."""
|
||||
import sphinx
|
||||
import jinja2
|
||||
@ -240,8 +233,7 @@ def save_traceback(app):
|
||||
return path
|
||||
|
||||
|
||||
def get_module_source(modname):
|
||||
# type: (str) -> Tuple[str, str]
|
||||
def get_module_source(modname: str) -> Tuple[str, str]:
|
||||
"""Try to find the source code for a module.
|
||||
|
||||
Can return ('file', 'filename') in which case the source is in the given
|
||||
@ -288,8 +280,7 @@ def get_module_source(modname):
|
||||
return 'file', filename
|
||||
|
||||
|
||||
def get_full_modname(modname, attribute):
|
||||
# type: (str, str) -> str
|
||||
def get_full_modname(modname: str, attribute: str) -> str:
|
||||
if modname is None:
|
||||
# Prevents a TypeError: if the last getattr() call will return None
|
||||
# then it's better to return it directly
|
||||
@ -311,19 +302,16 @@ def get_full_modname(modname, attribute):
|
||||
_coding_re = re.compile(r'coding[:=]\s*([-\w.]+)')
|
||||
|
||||
|
||||
def detect_encoding(readline):
|
||||
# type: (Callable[[], bytes]) -> str
|
||||
def detect_encoding(readline: Callable[[], bytes]) -> str:
|
||||
"""Like tokenize.detect_encoding() from Py3k, but a bit simplified."""
|
||||
|
||||
def read_or_stop():
|
||||
# type: () -> bytes
|
||||
def read_or_stop() -> bytes:
|
||||
try:
|
||||
return readline()
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def get_normal_name(orig_enc):
|
||||
# type: (str) -> str
|
||||
def get_normal_name(orig_enc: str) -> str:
|
||||
"""Imitates get_normal_name in tokenizer.c."""
|
||||
# Only care about the first 12 characters.
|
||||
enc = orig_enc[:12].lower().replace('_', '-')
|
||||
@ -334,8 +322,7 @@ def detect_encoding(readline):
|
||||
return 'iso-8859-1'
|
||||
return orig_enc
|
||||
|
||||
def find_cookie(line):
|
||||
# type: (bytes) -> str
|
||||
def find_cookie(line: bytes) -> str:
|
||||
try:
|
||||
line_string = line.decode('ascii')
|
||||
except UnicodeDecodeError:
|
||||
@ -368,12 +355,10 @@ def detect_encoding(readline):
|
||||
class UnicodeDecodeErrorHandler:
|
||||
"""Custom error handler for open() that warns and replaces."""
|
||||
|
||||
def __init__(self, docname):
|
||||
# type: (str) -> None
|
||||
def __init__(self, docname: str) -> None:
|
||||
self.docname = docname
|
||||
|
||||
def __call__(self, error):
|
||||
# type: (UnicodeDecodeError) -> Tuple[Union[str, str], int]
|
||||
def __call__(self, error: UnicodeDecodeError) -> Tuple[str, int]:
|
||||
linestart = error.object.rfind(b'\n', 0, error.start)
|
||||
lineend = error.object.find(b'\n', error.start)
|
||||
if lineend == -1:
|
||||
@ -393,26 +378,22 @@ class Tee:
|
||||
"""
|
||||
File-like object writing to two streams.
|
||||
"""
|
||||
def __init__(self, stream1, stream2):
|
||||
# type: (IO, IO) -> None
|
||||
def __init__(self, stream1: IO, stream2: IO) -> None:
|
||||
self.stream1 = stream1
|
||||
self.stream2 = stream2
|
||||
|
||||
def write(self, text):
|
||||
# type: (str) -> None
|
||||
def write(self, text: str) -> None:
|
||||
self.stream1.write(text)
|
||||
self.stream2.write(text)
|
||||
|
||||
def flush(self):
|
||||
# type: () -> None
|
||||
def flush(self) -> None:
|
||||
if hasattr(self.stream1, 'flush'):
|
||||
self.stream1.flush()
|
||||
if hasattr(self.stream2, 'flush'):
|
||||
self.stream2.flush()
|
||||
|
||||
|
||||
def parselinenos(spec, total):
|
||||
# type: (str, int) -> List[int]
|
||||
def parselinenos(spec: str, total: int) -> List[int]:
|
||||
"""Parse a line number spec (such as "1,2,4-6") and return a list of
|
||||
wanted line numbers.
|
||||
"""
|
||||
@ -439,8 +420,7 @@ def parselinenos(spec, total):
|
||||
return items
|
||||
|
||||
|
||||
def force_decode(string, encoding):
|
||||
# type: (str, str) -> str
|
||||
def force_decode(string: str, encoding: str) -> str:
|
||||
"""Forcibly get a unicode string out of a bytestring."""
|
||||
warnings.warn('force_decode() is deprecated.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
@ -458,26 +438,22 @@ def force_decode(string, encoding):
|
||||
|
||||
|
||||
class attrdict(dict):
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
warnings.warn('The attrdict class is deprecated.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
|
||||
def __getattr__(self, key):
|
||||
# type: (str) -> str
|
||||
def __getattr__(self, key: str) -> str:
|
||||
return self[key]
|
||||
|
||||
def __setattr__(self, key, val):
|
||||
# type: (str, str) -> None
|
||||
def __setattr__(self, key: str, val: str) -> None:
|
||||
self[key] = val
|
||||
|
||||
def __delattr__(self, key):
|
||||
# type: (str) -> None
|
||||
def __delattr__(self, key: str) -> None:
|
||||
del self[key]
|
||||
|
||||
|
||||
def rpartition(s, t):
|
||||
# type: (str, str) -> Tuple[str, str]
|
||||
def rpartition(s: str, t: str) -> Tuple[str, str]:
|
||||
"""Similar to str.rpartition from 2.5, but doesn't return the separator."""
|
||||
i = s.rfind(t)
|
||||
if i != -1:
|
||||
@ -485,8 +461,7 @@ def rpartition(s, t):
|
||||
return '', s
|
||||
|
||||
|
||||
def split_into(n, type, value):
|
||||
# type: (int, str, str) -> List[str]
|
||||
def split_into(n: int, type: str, value: str) -> List[str]:
|
||||
"""Split an index entry into a given number of parts at semicolons."""
|
||||
parts = [x.strip() for x in value.split(';', n - 1)]
|
||||
if sum(1 for part in parts if part) < n:
|
||||
@ -494,8 +469,7 @@ def split_into(n, type, value):
|
||||
return parts
|
||||
|
||||
|
||||
def split_index_msg(type, value):
|
||||
# type: (str, str) -> List[str]
|
||||
def split_index_msg(type: str, value: str) -> List[str]:
|
||||
# new entry types must be listed in directives/other.py!
|
||||
if type == 'single':
|
||||
try:
|
||||
@ -516,8 +490,7 @@ def split_index_msg(type, value):
|
||||
return result
|
||||
|
||||
|
||||
def format_exception_cut_frames(x=1):
|
||||
# type: (int) -> str
|
||||
def format_exception_cut_frames(x: int = 1) -> str:
|
||||
"""Format an exception with traceback, but only the last x frames."""
|
||||
typ, val, tb = sys.exc_info()
|
||||
# res = ['Traceback (most recent call last):\n']
|
||||
@ -533,19 +506,16 @@ class PeekableIterator:
|
||||
An iterator which wraps any iterable and makes it possible to peek to see
|
||||
what's the next item.
|
||||
"""
|
||||
def __init__(self, iterable):
|
||||
# type: (Iterable) -> None
|
||||
def __init__(self, iterable: Iterable) -> None:
|
||||
self.remaining = deque() # type: deque
|
||||
self._iterator = iter(iterable)
|
||||
warnings.warn('PeekableIterator is deprecated.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
|
||||
def __iter__(self):
|
||||
# type: () -> PeekableIterator
|
||||
def __iter__(self) -> "PeekableIterator":
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
# type: () -> Any
|
||||
def __next__(self) -> Any:
|
||||
"""Return the next item from the iterator."""
|
||||
if self.remaining:
|
||||
return self.remaining.popleft()
|
||||
@ -553,23 +523,20 @@ class PeekableIterator:
|
||||
|
||||
next = __next__ # Python 2 compatibility
|
||||
|
||||
def push(self, item):
|
||||
# type: (Any) -> None
|
||||
def push(self, item: Any) -> None:
|
||||
"""Push the `item` on the internal stack, it will be returned on the
|
||||
next :meth:`next` call.
|
||||
"""
|
||||
self.remaining.append(item)
|
||||
|
||||
def peek(self):
|
||||
# type: () -> Any
|
||||
def peek(self) -> Any:
|
||||
"""Return the next item without changing the state of the iterator."""
|
||||
item = next(self)
|
||||
self.push(item)
|
||||
return item
|
||||
|
||||
|
||||
def import_object(objname, source=None):
|
||||
# type: (str, str) -> Any
|
||||
def import_object(objname: str, source: str = None) -> Any:
|
||||
"""Import python object by qualname."""
|
||||
try:
|
||||
objpath = objname.split('.')
|
||||
@ -592,8 +559,7 @@ def import_object(objname, source=None):
|
||||
raise ExtensionError('Could not import %s' % objname, exc)
|
||||
|
||||
|
||||
def encode_uri(uri):
|
||||
# type: (str) -> str
|
||||
def encode_uri(uri: str) -> str:
|
||||
split = list(urlsplit(uri))
|
||||
split[1] = split[1].encode('idna').decode('ascii')
|
||||
split[2] = quote_plus(split[2].encode(), '/')
|
||||
@ -602,8 +568,7 @@ def encode_uri(uri):
|
||||
return urlunsplit(split)
|
||||
|
||||
|
||||
def display_chunk(chunk):
|
||||
# type: (Any) -> str
|
||||
def display_chunk(chunk: Any) -> str:
|
||||
if isinstance(chunk, (list, tuple)):
|
||||
if len(chunk) == 1:
|
||||
return str(chunk[0])
|
||||
@ -611,8 +576,8 @@ def display_chunk(chunk):
|
||||
return str(chunk)
|
||||
|
||||
|
||||
def old_status_iterator(iterable, summary, color="darkgreen", stringify_func=display_chunk):
|
||||
# type: (Iterable, str, str, Callable[[Any], str]) -> Iterator
|
||||
def old_status_iterator(iterable: Iterable, summary: str, color: str = "darkgreen",
|
||||
stringify_func: Callable[[Any], str] = display_chunk) -> Iterator:
|
||||
l = 0
|
||||
for item in iterable:
|
||||
if l == 0:
|
||||
@ -626,9 +591,9 @@ def old_status_iterator(iterable, summary, color="darkgreen", stringify_func=dis
|
||||
|
||||
|
||||
# new version with progress info
|
||||
def status_iterator(iterable, summary, color="darkgreen", length=0, verbosity=0,
|
||||
stringify_func=display_chunk):
|
||||
# type: (Iterable, str, str, int, int, Callable[[Any], str]) -> Iterable
|
||||
def status_iterator(iterable: Iterable, summary: str, color: str = "darkgreen",
|
||||
length: int = 0, verbosity: int = 0,
|
||||
stringify_func: Callable[[Any], str] = display_chunk) -> Iterable:
|
||||
if length == 0:
|
||||
yield from old_status_iterator(iterable, summary, color, stringify_func)
|
||||
return
|
||||
@ -652,16 +617,13 @@ class SkipProgressMessage(Exception):
|
||||
|
||||
|
||||
class progress_message:
|
||||
def __init__(self, message):
|
||||
# type: (str) -> None
|
||||
def __init__(self, message: str) -> None:
|
||||
self.message = message
|
||||
|
||||
def __enter__(self):
|
||||
# type: () -> None
|
||||
def __enter__(self) -> None:
|
||||
logger.info(bold(self.message + '... '), nonl=True)
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
# type: (Type[Exception], Exception, Any) -> bool
|
||||
def __exit__(self, exc_type: Type[Exception], exc_value: Exception, traceback: Any) -> bool: # NOQA
|
||||
if isinstance(exc_value, SkipProgressMessage):
|
||||
logger.info(__('skipped'))
|
||||
if exc_value.args:
|
||||
@ -674,8 +636,7 @@ class progress_message:
|
||||
|
||||
return False
|
||||
|
||||
def __call__(self, f):
|
||||
# type: (Callable) -> Callable
|
||||
def __call__(self, f: Callable) -> Callable:
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
with self:
|
||||
@ -684,8 +645,7 @@ class progress_message:
|
||||
return wrapper
|
||||
|
||||
|
||||
def epoch_to_rfc1123(epoch):
|
||||
# type: (float) -> str
|
||||
def epoch_to_rfc1123(epoch: float) -> str:
|
||||
"""Convert datetime format epoch to RFC1123."""
|
||||
from babel.dates import format_datetime
|
||||
|
||||
@ -694,13 +654,11 @@ def epoch_to_rfc1123(epoch):
|
||||
return format_datetime(dt, fmt, locale='en') + ' GMT'
|
||||
|
||||
|
||||
def rfc1123_to_epoch(rfc1123):
|
||||
# type: (str) -> float
|
||||
def rfc1123_to_epoch(rfc1123: str) -> float:
|
||||
return mktime(strptime(rfc1123, '%a, %d %b %Y %H:%M:%S %Z'))
|
||||
|
||||
|
||||
def xmlname_checker():
|
||||
# type: () -> Pattern
|
||||
def xmlname_checker() -> Pattern:
|
||||
# https://www.w3.org/TR/REC-xml/#NT-Name
|
||||
name_start_chars = [
|
||||
':', ['A', 'Z'], '_', ['a', 'z'], ['\u00C0', '\u00D6'],
|
||||
@ -714,8 +672,7 @@ def xmlname_checker():
|
||||
['\u203F', '\u2040']
|
||||
]
|
||||
|
||||
def convert(entries, splitter='|'):
|
||||
# type: (Any, str) -> str
|
||||
def convert(entries: Any, splitter: str = '|') -> str:
|
||||
results = []
|
||||
for entry in entries:
|
||||
if isinstance(entry, list):
|
||||
|
@ -10,22 +10,21 @@
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Any, Dict
|
||||
|
||||
from docutils.utils import get_source_line
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.config import Config
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
from sphinx.transforms import SphinxTransform
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Dict # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.config import Config # NOQA
|
||||
from sphinx.application import Sphinx
|
||||
|
||||
|
||||
def register_application_for_autosummary(app):
|
||||
# type: (Sphinx) -> None
|
||||
def register_application_for_autosummary(app: "Sphinx") -> None:
|
||||
"""Register application object to autosummary module.
|
||||
|
||||
Since Sphinx-1.7, documenters and attrgetters are registered into
|
||||
@ -42,8 +41,7 @@ class IndexEntriesMigrator(SphinxTransform):
|
||||
"""Migrating indexentries from old style (4columns) to new style (5columns)."""
|
||||
default_priority = 700
|
||||
|
||||
def apply(self, **kwargs):
|
||||
# type: (Any) -> None
|
||||
def apply(self, **kwargs) -> None:
|
||||
for node in self.document.traverse(addnodes.index):
|
||||
for i, entries in enumerate(node['entries']):
|
||||
if len(entries) == 4:
|
||||
@ -53,8 +51,7 @@ class IndexEntriesMigrator(SphinxTransform):
|
||||
node['entries'][i] = entries + (None,)
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[str, Any]
|
||||
def setup(app: "Sphinx") -> Dict[str, Any]:
|
||||
app.add_transform(IndexEntriesMigrator)
|
||||
app.connect('builder-inited', register_application_for_autosummary)
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from typing import Dict
|
||||
|
||||
try:
|
||||
# check if colorama is installed to support color on Windows
|
||||
@ -18,23 +19,17 @@ try:
|
||||
except ImportError:
|
||||
colorama = None
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Dict # NOQA
|
||||
|
||||
|
||||
_ansi_re = re.compile('\x1b\\[(\\d\\d;){0,2}\\d\\dm')
|
||||
codes = {} # type: Dict[str, str]
|
||||
|
||||
|
||||
def terminal_safe(s):
|
||||
# type: (str) -> str
|
||||
def terminal_safe(s: str) -> str:
|
||||
"""safely encode a string for printing to the terminal."""
|
||||
return s.encode('ascii', 'backslashreplace').decode('ascii')
|
||||
|
||||
|
||||
def get_terminal_width():
|
||||
# type: () -> int
|
||||
def get_terminal_width() -> int:
|
||||
"""Borrowed from the py lib."""
|
||||
try:
|
||||
import termios
|
||||
@ -53,8 +48,7 @@ def get_terminal_width():
|
||||
_tw = get_terminal_width()
|
||||
|
||||
|
||||
def term_width_line(text):
|
||||
# type: (str) -> str
|
||||
def term_width_line(text: str) -> str:
|
||||
if not codes:
|
||||
# if no coloring, don't output fancy backspaces
|
||||
return text + '\n'
|
||||
@ -63,8 +57,7 @@ def term_width_line(text):
|
||||
return text.ljust(_tw + len(text) - len(_ansi_re.sub('', text))) + '\r'
|
||||
|
||||
|
||||
def color_terminal():
|
||||
# type: () -> bool
|
||||
def color_terminal() -> bool:
|
||||
if sys.platform == 'win32' and colorama is not None:
|
||||
colorama.init()
|
||||
return True
|
||||
@ -80,21 +73,18 @@ def color_terminal():
|
||||
return False
|
||||
|
||||
|
||||
def nocolor():
|
||||
# type: () -> None
|
||||
def nocolor() -> None:
|
||||
if sys.platform == 'win32' and colorama is not None:
|
||||
colorama.deinit()
|
||||
codes.clear()
|
||||
|
||||
|
||||
def coloron():
|
||||
# type: () -> None
|
||||
def coloron() -> None:
|
||||
codes.update(_orig_codes)
|
||||
|
||||
|
||||
def colorize(name, text, input_mode=False):
|
||||
# type: (str, str, bool) -> str
|
||||
def escseq(name):
|
||||
def colorize(name: str, text: str, input_mode: bool = False) -> str:
|
||||
def escseq(name: str) -> str:
|
||||
# Wrap escape sequence with ``\1`` and ``\2`` to let readline know
|
||||
# it is non-printable characters
|
||||
# ref: https://tiswww.case.edu/php/chet/readline/readline.html
|
||||
@ -109,15 +99,12 @@ def colorize(name, text, input_mode=False):
|
||||
return escseq(name) + text + escseq('reset')
|
||||
|
||||
|
||||
def strip_colors(s):
|
||||
# type: (str) -> str
|
||||
def strip_colors(s: str) -> str:
|
||||
return re.compile('\x1b.*?m').sub('', s)
|
||||
|
||||
|
||||
def create_color_func(name):
|
||||
# type: (str) -> None
|
||||
def inner(text):
|
||||
# type: (str) -> str
|
||||
def create_color_func(name: str) -> None:
|
||||
def inner(text: str) -> str:
|
||||
return colorize(name, text)
|
||||
globals()[name] = inner
|
||||
|
||||
|
@ -10,23 +10,23 @@
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from typing import List, Tuple, cast
|
||||
from typing import Any, Dict, List, Tuple, Type, Union
|
||||
from typing import cast
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Node
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
from sphinx.util.typing import TextlikeNode
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Dict, Type, Union # NOQA
|
||||
from sphinx.directive import ObjectDescription # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
from sphinx.util.typing import TextlikeNode # NOQA
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.directive import ObjectDescription
|
||||
|
||||
|
||||
def _is_single_paragraph(node):
|
||||
# type: (nodes.field_body) -> bool
|
||||
def _is_single_paragraph(node: nodes.field_body) -> bool:
|
||||
"""True if the node only contains one paragraph (and system messages)."""
|
||||
if len(node) == 0:
|
||||
return False
|
||||
@ -55,9 +55,8 @@ class Field:
|
||||
is_grouped = False
|
||||
is_typed = False
|
||||
|
||||
def __init__(self, name, names=(), label=None, has_arg=True, rolename=None,
|
||||
bodyrolename=None):
|
||||
# type: (str, Tuple[str, ...], str, bool, str, str) -> None
|
||||
def __init__(self, name: str, names: Tuple[str, ...] = (), label: str = None,
|
||||
has_arg: bool = True, rolename: str = None, bodyrolename: str = None) -> None:
|
||||
self.name = name
|
||||
self.names = names
|
||||
self.label = label
|
||||
@ -65,15 +64,9 @@ class Field:
|
||||
self.rolename = rolename
|
||||
self.bodyrolename = bodyrolename
|
||||
|
||||
def make_xref(self,
|
||||
rolename, # type: str
|
||||
domain, # type: str
|
||||
target, # type: str
|
||||
innernode=addnodes.literal_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] = addnodes.literal_emphasis,
|
||||
contnode: Node = None, env: "BuildEnvironment" = None) -> Node:
|
||||
if not rolename:
|
||||
return contnode or innernode(target, target)
|
||||
refnode = addnodes.pending_xref('', refdomain=domain, refexplicit=False,
|
||||
@ -83,28 +76,16 @@ class Field:
|
||||
env.get_domain(domain).process_field_xref(refnode)
|
||||
return refnode
|
||||
|
||||
def make_xrefs(self,
|
||||
rolename, # type: str
|
||||
domain, # type: str
|
||||
target, # type: str
|
||||
innernode=addnodes.literal_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] = addnodes.literal_emphasis,
|
||||
contnode: Node = None, env: "BuildEnvironment" = None) -> List[Node]:
|
||||
return [self.make_xref(rolename, domain, target, innernode, contnode, env)]
|
||||
|
||||
def make_entry(self, fieldarg, content):
|
||||
# type: (str, List[nodes.Node]) -> Tuple[str, List[nodes.Node]]
|
||||
def make_entry(self, fieldarg: str, content: List[Node]) -> Tuple[str, List[Node]]:
|
||||
return (fieldarg, content)
|
||||
|
||||
def make_field(self,
|
||||
types, # type: Dict[str, List[nodes.Node]]
|
||||
domain, # type: str
|
||||
item, # type: Tuple
|
||||
env=None, # type: BuildEnvironment
|
||||
):
|
||||
# type: (...) -> nodes.field
|
||||
def make_field(self, types: Dict[str, List[Node]], domain: str,
|
||||
item: Tuple, env: "BuildEnvironment" = None) -> nodes.field:
|
||||
fieldarg, content = item
|
||||
fieldname = nodes.field_name('', self.label)
|
||||
if fieldarg:
|
||||
@ -138,19 +119,13 @@ class GroupedField(Field):
|
||||
is_grouped = True
|
||||
list_type = nodes.bullet_list
|
||||
|
||||
def __init__(self, name, names=(), label=None, rolename=None,
|
||||
can_collapse=False):
|
||||
# type: (str, Tuple[str, ...], str, str, bool) -> None
|
||||
def __init__(self, name: str, names: Tuple[str, ...] = (), label: str = None,
|
||||
rolename: str = None, can_collapse: bool = False) -> None:
|
||||
super().__init__(name, names, label, True, rolename)
|
||||
self.can_collapse = can_collapse
|
||||
|
||||
def make_field(self,
|
||||
types, # type: Dict[str, List[nodes.Node]]
|
||||
domain, # type: str
|
||||
items, # type: Tuple
|
||||
env=None, # type: BuildEnvironment
|
||||
):
|
||||
# type: (...) -> nodes.field
|
||||
def make_field(self, types: Dict[str, List[Node]], domain: str,
|
||||
items: Tuple, env: "BuildEnvironment" = None) -> nodes.field:
|
||||
fieldname = nodes.field_name('', self.label)
|
||||
listnode = self.list_type()
|
||||
for fieldarg, content in items:
|
||||
@ -191,22 +166,16 @@ class TypedField(GroupedField):
|
||||
"""
|
||||
is_typed = True
|
||||
|
||||
def __init__(self, name, names=(), typenames=(), label=None,
|
||||
rolename=None, typerolename=None, can_collapse=False):
|
||||
# type: (str, Tuple[str, ...], Tuple[str, ...], str, str, str, bool) -> None
|
||||
def __init__(self, name: str, names: Tuple[str, ...] = (), typenames: Tuple[str, ...] = (),
|
||||
label: str = None, rolename: str = None, typerolename: str = None,
|
||||
can_collapse: bool = False) -> None:
|
||||
super().__init__(name, names, label, rolename, can_collapse)
|
||||
self.typenames = typenames
|
||||
self.typerolename = typerolename
|
||||
|
||||
def make_field(self,
|
||||
types, # type: Dict[str, List[nodes.Node]]
|
||||
domain, # type: str
|
||||
items, # type: Tuple
|
||||
env=None, # type: BuildEnvironment
|
||||
):
|
||||
# type: (...) -> nodes.field
|
||||
def handle_item(fieldarg, content):
|
||||
# type: (str, str) -> nodes.paragraph
|
||||
def make_field(self, types: Dict[str, List[Node]], domain: str,
|
||||
items: Tuple, env: "BuildEnvironment" = None) -> nodes.field:
|
||||
def handle_item(fieldarg: str, content: str) -> nodes.paragraph:
|
||||
par = nodes.paragraph()
|
||||
par.extend(self.make_xrefs(self.rolename, domain, fieldarg,
|
||||
addnodes.literal_strong, env=env))
|
||||
@ -246,13 +215,11 @@ class DocFieldTransformer:
|
||||
"""
|
||||
typemap = None # type: Dict[str, Tuple[Field, bool]]
|
||||
|
||||
def __init__(self, directive):
|
||||
# type: (ObjectDescription) -> None
|
||||
def __init__(self, directive: "ObjectDescription") -> None:
|
||||
self.directive = directive
|
||||
self.typemap = directive.get_field_type_map()
|
||||
|
||||
def preprocess_fieldtypes(self, types):
|
||||
# type: (List[Field]) -> Dict[str, Tuple[Field, bool]]
|
||||
def preprocess_fieldtypes(self, types: List[Field]) -> Dict[str, Tuple[Field, bool]]:
|
||||
warnings.warn('DocFieldTransformer.preprocess_fieldtypes() is deprecated.',
|
||||
RemovedInSphinx40Warning)
|
||||
typemap = {}
|
||||
@ -265,16 +232,14 @@ class DocFieldTransformer:
|
||||
typemap[name] = typed_field, True
|
||||
return typemap
|
||||
|
||||
def transform_all(self, node):
|
||||
# type: (addnodes.desc_content) -> None
|
||||
def transform_all(self, node: addnodes.desc_content) -> None:
|
||||
"""Transform all field list children of a node."""
|
||||
# don't traverse, only handle field lists that are immediate children
|
||||
for child in node:
|
||||
if isinstance(child, nodes.field_list):
|
||||
self.transform(child)
|
||||
|
||||
def transform(self, node):
|
||||
# type: (nodes.field_list) -> None
|
||||
def transform(self, node: nodes.field_list) -> None:
|
||||
"""Transform a single field list *node*."""
|
||||
typemap = self.typemap
|
||||
|
||||
|
@ -9,14 +9,10 @@
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import List # NOQA
|
||||
from typing import List
|
||||
|
||||
|
||||
def prepare_docstring(s, ignore=1, tabsize=8):
|
||||
# type: (str, int, int) -> List[str]
|
||||
def prepare_docstring(s: str, ignore: int = 1, tabsize: int = 8) -> List[str]:
|
||||
"""Convert a docstring into lines of parseable reST. Remove common leading
|
||||
indentation, where the indentation of a given number of lines (usually just
|
||||
one) is ignored.
|
||||
@ -49,8 +45,7 @@ def prepare_docstring(s, ignore=1, tabsize=8):
|
||||
return lines
|
||||
|
||||
|
||||
def prepare_commentdoc(s):
|
||||
# type: (str) -> List[str]
|
||||
def prepare_commentdoc(s: str) -> List[str]:
|
||||
"""Extract documentation comment lines (starting with #:) and return them
|
||||
as a list of lines. Returns an empty list if there is no documentation.
|
||||
"""
|
||||
|
@ -14,31 +14,31 @@ from contextlib import contextmanager
|
||||
from copy import copy
|
||||
from distutils.version import LooseVersion
|
||||
from os import path
|
||||
from typing import IO, cast
|
||||
from types import ModuleType
|
||||
from typing import Any, Callable, Dict, Generator, IO, List, Set, Tuple, Type
|
||||
from typing import cast
|
||||
|
||||
import docutils
|
||||
from docutils import nodes
|
||||
from docutils.io import FileOutput
|
||||
from docutils.nodes import Element, Node, system_message
|
||||
from docutils.parsers.rst import Directive, directives, roles
|
||||
from docutils.statemachine import StateMachine
|
||||
from docutils.parsers.rst.states import Inliner
|
||||
from docutils.statemachine import StateMachine, State, StringList
|
||||
from docutils.utils import Reporter, unescape
|
||||
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.typing import RoleFunction
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(\\d+)?\\) ')
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from types import ModuleType # NOQA
|
||||
from typing import Any, Callable, Dict, Generator, List, Set, Tuple, Type # NOQA
|
||||
from docutils.parsers.rst.states import Inliner # NOQA
|
||||
from docutils.statemachine import State, StringList # NOQA
|
||||
from sphinx.builders import Builder # NOQA
|
||||
from sphinx.config import Config # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
from sphinx.util.typing import RoleFunction # NOQA
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.config import Config
|
||||
from sphinx.environment import BuildEnvironment
|
||||
|
||||
|
||||
__version_info__ = tuple(LooseVersion(docutils.__version__).version)
|
||||
@ -46,8 +46,7 @@ additional_nodes = set() # type: Set[Type[nodes.Element]]
|
||||
|
||||
|
||||
@contextmanager
|
||||
def docutils_namespace():
|
||||
# type: () -> Generator[None, None, None]
|
||||
def docutils_namespace() -> Generator[None, None, None]:
|
||||
"""Create namespace for reST parsers."""
|
||||
try:
|
||||
_directives = copy(directives._directives) # type: ignore
|
||||
@ -63,14 +62,12 @@ def docutils_namespace():
|
||||
additional_nodes.discard(node)
|
||||
|
||||
|
||||
def is_directive_registered(name):
|
||||
# type: (str) -> bool
|
||||
def is_directive_registered(name: str) -> bool:
|
||||
"""Check the *name* directive is already registered."""
|
||||
return name in directives._directives # type: ignore
|
||||
|
||||
|
||||
def register_directive(name, directive):
|
||||
# type: (str, Type[Directive]) -> None
|
||||
def register_directive(name: str, directive: Type[Directive]) -> None:
|
||||
"""Register a directive to docutils.
|
||||
|
||||
This modifies global state of docutils. So it is better to use this
|
||||
@ -79,14 +76,12 @@ def register_directive(name, directive):
|
||||
directives.register_directive(name, directive)
|
||||
|
||||
|
||||
def is_role_registered(name):
|
||||
# type: (str) -> bool
|
||||
def is_role_registered(name: str) -> bool:
|
||||
"""Check the *name* role is already registered."""
|
||||
return name in roles._roles # type: ignore
|
||||
|
||||
|
||||
def register_role(name, role):
|
||||
# type: (str, RoleFunction) -> None
|
||||
def register_role(name: str, role: RoleFunction) -> None:
|
||||
"""Register a role to docutils.
|
||||
|
||||
This modifies global state of docutils. So it is better to use this
|
||||
@ -95,20 +90,17 @@ def register_role(name, role):
|
||||
roles.register_local_role(name, role)
|
||||
|
||||
|
||||
def unregister_role(name):
|
||||
# type: (str) -> None
|
||||
def unregister_role(name: str) -> None:
|
||||
"""Unregister a role from docutils."""
|
||||
roles._roles.pop(name, None) # type: ignore
|
||||
|
||||
|
||||
def is_node_registered(node):
|
||||
# type: (Type[nodes.Element]) -> bool
|
||||
def is_node_registered(node: Type[Element]) -> bool:
|
||||
"""Check the *node* is already registered."""
|
||||
return hasattr(nodes.GenericNodeVisitor, 'visit_' + node.__name__)
|
||||
|
||||
|
||||
def register_node(node):
|
||||
# type: (Type[nodes.Element]) -> None
|
||||
def register_node(node: Type[Element]) -> None:
|
||||
"""Register a node to docutils.
|
||||
|
||||
This modifies global state of some visitors. So it is better to use this
|
||||
@ -119,8 +111,7 @@ def register_node(node):
|
||||
additional_nodes.add(node)
|
||||
|
||||
|
||||
def unregister_node(node):
|
||||
# type: (Type[nodes.Element]) -> None
|
||||
def unregister_node(node: Type[Element]) -> None:
|
||||
"""Unregister a node from docutils.
|
||||
|
||||
This is inverse of ``nodes._add_nodes_class_names()``.
|
||||
@ -133,8 +124,7 @@ def unregister_node(node):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def patched_get_language():
|
||||
# type: () -> Generator[None, None, None]
|
||||
def patched_get_language() -> Generator[None, None, None]:
|
||||
"""Patch docutils.languages.get_language() temporarily.
|
||||
|
||||
This ignores the second argument ``reporter`` to suppress warnings.
|
||||
@ -142,8 +132,7 @@ def patched_get_language():
|
||||
"""
|
||||
from docutils.languages import get_language
|
||||
|
||||
def patched_get_language(language_code, reporter=None):
|
||||
# type: (str, Reporter) -> Any
|
||||
def patched_get_language(language_code: str, reporter: Reporter = None) -> Any:
|
||||
return get_language(language_code)
|
||||
|
||||
try:
|
||||
@ -155,8 +144,7 @@ def patched_get_language():
|
||||
|
||||
|
||||
@contextmanager
|
||||
def using_user_docutils_conf(confdir):
|
||||
# type: (str) -> Generator[None, None, None]
|
||||
def using_user_docutils_conf(confdir: str) -> Generator[None, None, None]:
|
||||
"""Let docutils know the location of ``docutils.conf`` for Sphinx."""
|
||||
try:
|
||||
docutilsconfig = os.environ.get('DOCUTILSCONFIG', None)
|
||||
@ -172,8 +160,7 @@ def using_user_docutils_conf(confdir):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def patch_docutils(confdir=None):
|
||||
# type: (str) -> Generator[None, None, None]
|
||||
def patch_docutils(confdir: str = None) -> Generator[None, None, None]:
|
||||
"""Patch to docutils temporarily."""
|
||||
with patched_get_language(), using_user_docutils_conf(confdir):
|
||||
yield
|
||||
@ -187,35 +174,30 @@ class sphinx_domains:
|
||||
"""Monkey-patch directive and role dispatch, so that domain-specific
|
||||
markup takes precedence.
|
||||
"""
|
||||
def __init__(self, env):
|
||||
# type: (BuildEnvironment) -> None
|
||||
def __init__(self, env: "BuildEnvironment") -> None:
|
||||
self.env = env
|
||||
self.directive_func = None # type: Callable
|
||||
self.roles_func = None # type: Callable
|
||||
|
||||
def __enter__(self):
|
||||
# type: () -> None
|
||||
def __enter__(self) -> None:
|
||||
self.enable()
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
# type: (str, str, str) -> None
|
||||
def __exit__(self, exc_type: Type[Exception], exc_value: Exception, traceback: Any) -> bool: # NOQA
|
||||
self.disable()
|
||||
return True
|
||||
|
||||
def enable(self):
|
||||
# type: () -> None
|
||||
def enable(self) -> None:
|
||||
self.directive_func = directives.directive
|
||||
self.role_func = roles.role
|
||||
|
||||
directives.directive = self.lookup_directive # type: ignore
|
||||
roles.role = self.lookup_role # type: ignore
|
||||
|
||||
def disable(self):
|
||||
# type: () -> None
|
||||
def disable(self) -> None:
|
||||
directives.directive = self.directive_func
|
||||
roles.role = self.role_func
|
||||
|
||||
def lookup_domain_element(self, type, name):
|
||||
# type: (str, str) -> Any
|
||||
def lookup_domain_element(self, type: str, name: str) -> Any:
|
||||
"""Lookup a markup element (directive or role), given its name which can
|
||||
be a full name (with domain).
|
||||
"""
|
||||
@ -243,15 +225,13 @@ class sphinx_domains:
|
||||
|
||||
raise ElementLookupError
|
||||
|
||||
def lookup_directive(self, name, lang_module, document):
|
||||
# type: (str, ModuleType, nodes.document) -> Tuple[Type[Directive], List[nodes.system_message]] # NOQA
|
||||
def lookup_directive(self, name: str, lang_module: ModuleType, document: nodes.document) -> Tuple[Type[Directive], List[system_message]]: # NOQA
|
||||
try:
|
||||
return self.lookup_domain_element('directive', name)
|
||||
except ElementLookupError:
|
||||
return self.directive_func(name, lang_module, document)
|
||||
|
||||
def lookup_role(self, name, lang_module, lineno, reporter):
|
||||
# type: (str, ModuleType, int, Reporter) -> Tuple[RoleFunction, List[nodes.system_message]] # NOQA
|
||||
def lookup_role(self, name: str, lang_module: ModuleType, lineno: int, reporter: Reporter) -> Tuple[RoleFunction, List[system_message]]: # NOQA
|
||||
try:
|
||||
return self.lookup_domain_element('role', name)
|
||||
except ElementLookupError:
|
||||
@ -259,8 +239,7 @@ class sphinx_domains:
|
||||
|
||||
|
||||
class WarningStream:
|
||||
def write(self, text):
|
||||
# type: (str) -> None
|
||||
def write(self, text: str) -> None:
|
||||
matched = report_re.search(text)
|
||||
if not matched:
|
||||
logger.warning(text.rstrip("\r\n"))
|
||||
@ -272,16 +251,14 @@ class WarningStream:
|
||||
|
||||
class LoggingReporter(Reporter):
|
||||
@classmethod
|
||||
def from_reporter(cls, reporter):
|
||||
# type: (Reporter) -> LoggingReporter
|
||||
def from_reporter(cls, reporter: Reporter) -> "LoggingReporter":
|
||||
"""Create an instance of LoggingReporter from other reporter object."""
|
||||
return cls(reporter.source, reporter.report_level, reporter.halt_level,
|
||||
reporter.debug_flag, reporter.error_handler)
|
||||
|
||||
def __init__(self, source, report_level=Reporter.WARNING_LEVEL,
|
||||
halt_level=Reporter.SEVERE_LEVEL, debug=False,
|
||||
error_handler='backslashreplace'):
|
||||
# type: (str, int, int, bool, str) -> None
|
||||
def __init__(self, source: str, report_level: int = Reporter.WARNING_LEVEL,
|
||||
halt_level: int = Reporter.SEVERE_LEVEL, debug: bool = False,
|
||||
error_handler: str = 'backslashreplace') -> None:
|
||||
stream = cast(IO, WarningStream())
|
||||
super().__init__(source, report_level, halt_level,
|
||||
stream, debug, error_handler=error_handler)
|
||||
@ -290,19 +267,16 @@ class LoggingReporter(Reporter):
|
||||
class NullReporter(Reporter):
|
||||
"""A dummy reporter; write nothing."""
|
||||
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
def __init__(self) -> None:
|
||||
super().__init__('', 999, 4)
|
||||
|
||||
|
||||
def is_html5_writer_available():
|
||||
# type: () -> bool
|
||||
def is_html5_writer_available() -> bool:
|
||||
return __version_info__ > (0, 13, 0)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def switch_source_input(state, content):
|
||||
# type: (State, StringList) -> Generator[None, None, None]
|
||||
def switch_source_input(state: State, content: StringList) -> Generator[None, None, None]:
|
||||
"""Switch current source input of state temporarily."""
|
||||
try:
|
||||
# remember the original ``get_source_and_line()`` method
|
||||
@ -322,13 +296,11 @@ def switch_source_input(state, content):
|
||||
class SphinxFileOutput(FileOutput):
|
||||
"""Better FileOutput class for Sphinx."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# type: (Any) -> None
|
||||
def __init__(self, **kwargs) -> None:
|
||||
self.overwrite_if_changed = kwargs.pop('overwrite_if_changed', False)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def write(self, data):
|
||||
# type: (str) -> str
|
||||
def write(self, data: str) -> str:
|
||||
if (self.destination_path and self.autoclose and 'b' not in self.mode and
|
||||
self.overwrite_if_changed and os.path.exists(self.destination_path)):
|
||||
with open(self.destination_path, encoding=self.encoding) as f:
|
||||
@ -349,19 +321,16 @@ class SphinxDirective(Directive):
|
||||
"""
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
# type: () -> BuildEnvironment
|
||||
def env(self) -> "BuildEnvironment":
|
||||
"""Reference to the :class:`.BuildEnvironment` object."""
|
||||
return self.state.document.settings.env
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
# type: () -> Config
|
||||
def config(self) -> "Config":
|
||||
"""Reference to the :class:`.Config` object."""
|
||||
return self.env.config
|
||||
|
||||
def set_source_info(self, node):
|
||||
# type: (nodes.Node) -> None
|
||||
def set_source_info(self, node: Node) -> None:
|
||||
"""Set source and line number to the node."""
|
||||
node.source, node.line = self.state_machine.get_source_and_line(self.lineno)
|
||||
|
||||
@ -384,8 +353,9 @@ class SphinxRole:
|
||||
content = None #: A list of strings, the directive content for customization
|
||||
#: (from the "role" directive).
|
||||
|
||||
def __call__(self, name, 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 __call__(self, name: str, rawtext: str, text: str, lineno: int,
|
||||
inliner: Inliner, options: Dict = {}, content: List[str] = []
|
||||
) -> Tuple[List[Node], List[system_message]]:
|
||||
self.rawtext = rawtext
|
||||
self.text = unescape(text)
|
||||
self.lineno = lineno
|
||||
@ -405,24 +375,20 @@ class SphinxRole:
|
||||
|
||||
return self.run()
|
||||
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
def run(self) -> Tuple[List[Node], List[system_message]]:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
# type: () -> BuildEnvironment
|
||||
def env(self) -> "BuildEnvironment":
|
||||
"""Reference to the :class:`.BuildEnvironment` object."""
|
||||
return self.inliner.document.settings.env
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
# type: () -> Config
|
||||
def config(self) -> "Config":
|
||||
"""Reference to the :class:`.Config` object."""
|
||||
return self.env.config
|
||||
|
||||
def set_source_info(self, node, lineno=None):
|
||||
# type: (nodes.Node, int) -> None
|
||||
def set_source_info(self, node: Node, lineno: int = None) -> None:
|
||||
if lineno is None:
|
||||
lineno = self.lineno
|
||||
|
||||
@ -444,8 +410,9 @@ class ReferenceRole(SphinxRole):
|
||||
# \x00 means the "<" was backslash-escaped
|
||||
explicit_title_re = re.compile(r'^(.+?)\s*(?<!\x00)<(.*?)>$', re.DOTALL)
|
||||
|
||||
def __call__(self, name, 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 __call__(self, name: str, rawtext: str, text: str, lineno: int,
|
||||
inliner: Inliner, options: Dict = {}, content: List[str] = []
|
||||
) -> Tuple[List[Node], List[system_message]]:
|
||||
matched = self.explicit_title_re.match(text)
|
||||
if matched:
|
||||
self.has_explicit_title = True
|
||||
@ -468,8 +435,7 @@ class SphinxTranslator(nodes.NodeVisitor):
|
||||
This class is strongly coupled with Sphinx.
|
||||
"""
|
||||
|
||||
def __init__(self, document, builder):
|
||||
# type: (nodes.document, Builder) -> None
|
||||
def __init__(self, document: nodes.document, builder: "Builder") -> None:
|
||||
super().__init__(document)
|
||||
self.builder = builder
|
||||
self.config = builder.config
|
||||
@ -481,8 +447,7 @@ class SphinxTranslator(nodes.NodeVisitor):
|
||||
__document_cache__ = None # type: nodes.document
|
||||
|
||||
|
||||
def new_document(source_path, settings=None):
|
||||
# type: (str, Any) -> nodes.document
|
||||
def new_document(source_path: str, settings: Any = None) -> nodes.document:
|
||||
"""Return a new empty document object. This is an alternative of docutils'.
|
||||
|
||||
This is a simple wrapper for ``docutils.utils.new_document()``. It
|
||||
|
@ -10,20 +10,20 @@
|
||||
|
||||
import os
|
||||
import posixpath
|
||||
from typing import Dict
|
||||
|
||||
from docutils.utils import relative_path
|
||||
|
||||
from sphinx.util.osutil import copyfile, ensuredir
|
||||
from sphinx.util.typing import PathMatcher
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Callable, Dict, Union # NOQA
|
||||
from sphinx.util.matching import Matcher # NOQA
|
||||
from sphinx.util.template import BaseRenderer # NOQA
|
||||
from sphinx.util.template import BaseRenderer
|
||||
|
||||
|
||||
def copy_asset_file(source, destination, context=None, renderer=None):
|
||||
# type: (str, str, Dict, BaseRenderer) -> None
|
||||
def copy_asset_file(source: str, destination: str,
|
||||
context: Dict = None, renderer: "BaseRenderer" = None) -> None:
|
||||
"""Copy an asset file to destination.
|
||||
|
||||
On copying, it expands the template variables if context argument is given and
|
||||
@ -55,8 +55,8 @@ def copy_asset_file(source, destination, context=None, renderer=None):
|
||||
copyfile(source, destination)
|
||||
|
||||
|
||||
def copy_asset(source, destination, excluded=lambda path: False, context=None, renderer=None):
|
||||
# type: (str, str, Union[Callable[[str], bool], Matcher], Dict, BaseRenderer) -> None
|
||||
def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda path: False,
|
||||
context: Dict = None, renderer: "BaseRenderer" = None) -> None:
|
||||
"""Copy asset files to destination recursively.
|
||||
|
||||
On copying, it expands the template variables if context argument is given and
|
||||
|
@ -14,6 +14,7 @@ import warnings
|
||||
from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from os import path
|
||||
from typing import Callable, Generator, List, Set, Tuple
|
||||
|
||||
import babel.dates
|
||||
from babel.messages.mofile import write_mo
|
||||
@ -26,13 +27,12 @@ from sphinx.util import logging
|
||||
from sphinx.util.matching import Matcher
|
||||
from sphinx.util.osutil import SEP, canon_path, relpath
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Callable, Generator, List, Set, Tuple # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
from sphinx.environment import BuildEnvironment
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LocaleFileInfoBase = namedtuple('CatalogInfo', 'base_dir,domain,charset')
|
||||
|
||||
@ -40,33 +40,27 @@ LocaleFileInfoBase = namedtuple('CatalogInfo', 'base_dir,domain,charset')
|
||||
class CatalogInfo(LocaleFileInfoBase):
|
||||
|
||||
@property
|
||||
def po_file(self):
|
||||
# type: () -> str
|
||||
def po_file(self) -> str:
|
||||
return self.domain + '.po'
|
||||
|
||||
@property
|
||||
def mo_file(self):
|
||||
# type: () -> str
|
||||
def mo_file(self) -> str:
|
||||
return self.domain + '.mo'
|
||||
|
||||
@property
|
||||
def po_path(self):
|
||||
# type: () -> str
|
||||
def po_path(self) -> str:
|
||||
return path.join(self.base_dir, self.po_file)
|
||||
|
||||
@property
|
||||
def mo_path(self):
|
||||
# type: () -> str
|
||||
def mo_path(self) -> str:
|
||||
return path.join(self.base_dir, self.mo_file)
|
||||
|
||||
def is_outdated(self):
|
||||
# type: () -> bool
|
||||
def is_outdated(self) -> bool:
|
||||
return (
|
||||
not path.exists(self.mo_path) or
|
||||
path.getmtime(self.mo_path) < path.getmtime(self.po_path))
|
||||
|
||||
def write_mo(self, locale):
|
||||
# type: (str) -> None
|
||||
def write_mo(self, locale: str) -> None:
|
||||
with open(self.po_path, encoding=self.charset) as file_po:
|
||||
try:
|
||||
po = read_po(file_po, locale)
|
||||
@ -84,16 +78,15 @@ class CatalogInfo(LocaleFileInfoBase):
|
||||
class CatalogRepository:
|
||||
"""A repository for message catalogs."""
|
||||
|
||||
def __init__(self, basedir, locale_dirs, language, encoding):
|
||||
# type: (str, List[str], str, str) -> None
|
||||
def __init__(self, basedir: str, locale_dirs: List[str],
|
||||
language: str, encoding: str) -> None:
|
||||
self.basedir = basedir
|
||||
self._locale_dirs = locale_dirs
|
||||
self.language = language
|
||||
self.encoding = encoding
|
||||
|
||||
@property
|
||||
def locale_dirs(self):
|
||||
# type: () -> Generator[str, None, None]
|
||||
def locale_dirs(self) -> Generator[str, None, None]:
|
||||
if not self.language:
|
||||
return
|
||||
|
||||
@ -103,8 +96,7 @@ class CatalogRepository:
|
||||
yield locale_dir
|
||||
|
||||
@property
|
||||
def pofiles(self):
|
||||
# type: () -> Generator[Tuple[str, str], None, None]
|
||||
def pofiles(self) -> Generator[Tuple[str, str], None, None]:
|
||||
for locale_dir in self.locale_dirs:
|
||||
basedir = path.join(locale_dir, self.language, 'LC_MESSAGES')
|
||||
for root, dirnames, filenames in os.walk(basedir):
|
||||
@ -119,15 +111,13 @@ class CatalogRepository:
|
||||
yield basedir, relpath(fullpath, basedir)
|
||||
|
||||
@property
|
||||
def catalogs(self):
|
||||
# type: () -> Generator[CatalogInfo, None, None]
|
||||
def catalogs(self) -> Generator[CatalogInfo, None, None]:
|
||||
for basedir, filename in self.pofiles:
|
||||
domain = canon_path(path.splitext(filename)[0])
|
||||
yield CatalogInfo(basedir, domain, self.encoding)
|
||||
|
||||
|
||||
def find_catalog(docname, compaction):
|
||||
# type: (str, bool) -> str
|
||||
def find_catalog(docname: str, compaction: bool) -> str:
|
||||
warnings.warn('find_catalog() is deprecated.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
if compaction:
|
||||
@ -138,8 +128,7 @@ def find_catalog(docname, compaction):
|
||||
return ret
|
||||
|
||||
|
||||
def docname_to_domain(docname, compation):
|
||||
# type: (str, bool) -> str
|
||||
def docname_to_domain(docname: str, compation: bool) -> str:
|
||||
"""Convert docname to domain for catalogs."""
|
||||
if compation:
|
||||
return docname.split(SEP, 1)[0]
|
||||
@ -147,8 +136,8 @@ def docname_to_domain(docname, compation):
|
||||
return docname
|
||||
|
||||
|
||||
def find_catalog_files(docname, srcdir, locale_dirs, lang, compaction):
|
||||
# type: (str, str, List[str], str, bool) -> List[str]
|
||||
def find_catalog_files(docname: str, srcdir: str, locale_dirs: List[str],
|
||||
lang: str, compaction: bool) -> List[str]:
|
||||
warnings.warn('find_catalog_files() is deprecated.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
if not(lang and locale_dirs):
|
||||
@ -161,9 +150,9 @@ def find_catalog_files(docname, srcdir, locale_dirs, lang, compaction):
|
||||
return files
|
||||
|
||||
|
||||
def find_catalog_source_files(locale_dirs, locale, domains=None, charset='utf-8',
|
||||
force_all=False, excluded=Matcher([])):
|
||||
# type: (List[str], str, List[str], str, bool, Matcher) -> Set[CatalogInfo]
|
||||
def find_catalog_source_files(locale_dirs: List[str], locale: str, domains: List[str] = None,
|
||||
charset: str = 'utf-8', force_all: bool = False,
|
||||
excluded: Matcher = Matcher([])) -> Set[CatalogInfo]:
|
||||
"""
|
||||
:param list locale_dirs:
|
||||
list of path as `['locale_dir1', 'locale_dir2', ...]` to find
|
||||
@ -252,8 +241,8 @@ date_format_mappings = {
|
||||
date_format_re = re.compile('(%s)' % '|'.join(date_format_mappings))
|
||||
|
||||
|
||||
def babel_format_date(date, format, locale, formatter=babel.dates.format_date):
|
||||
# type: (datetime, str, str, Callable) -> str
|
||||
def babel_format_date(date: datetime, format: str, locale: str,
|
||||
formatter: Callable = babel.dates.format_date) -> str:
|
||||
if locale is None:
|
||||
locale = 'en'
|
||||
|
||||
@ -273,8 +262,7 @@ def babel_format_date(date, format, locale, formatter=babel.dates.format_date):
|
||||
return format
|
||||
|
||||
|
||||
def format_date(format, date=None, language=None):
|
||||
# type: (str, datetime, str) -> str
|
||||
def format_date(format: str, date: datetime = None, language: str = None) -> str:
|
||||
if date is None:
|
||||
# If time is not specified, try to use $SOURCE_DATE_EPOCH variable
|
||||
# See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal
|
||||
@ -308,8 +296,7 @@ def format_date(format, date=None, language=None):
|
||||
return "".join(result)
|
||||
|
||||
|
||||
def get_image_filename_for_language(filename, env):
|
||||
# type: (str, BuildEnvironment) -> str
|
||||
def get_image_filename_for_language(filename: str, env: "BuildEnvironment") -> str:
|
||||
if not env.config.language:
|
||||
return filename
|
||||
|
||||
@ -328,8 +315,7 @@ def get_image_filename_for_language(filename, env):
|
||||
raise SphinxError('Invalid figure_language_filename: %r' % exc)
|
||||
|
||||
|
||||
def search_image_for_language(filename, env):
|
||||
# type: (str, BuildEnvironment) -> str
|
||||
def search_image_for_language(filename: str, env: "BuildEnvironment") -> str:
|
||||
if not env.config.language:
|
||||
return filename
|
||||
|
||||
|
@ -12,7 +12,7 @@ import base64
|
||||
import imghdr
|
||||
from collections import OrderedDict
|
||||
from os import path
|
||||
from typing import NamedTuple
|
||||
from typing import IO, NamedTuple, Tuple
|
||||
|
||||
import imagesize
|
||||
|
||||
@ -21,10 +21,6 @@ try:
|
||||
except ImportError:
|
||||
Image = None
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import IO, Tuple # NOQA
|
||||
|
||||
mime_suffixes = OrderedDict([
|
||||
('.gif', 'image/gif'),
|
||||
('.jpg', 'image/jpeg'),
|
||||
@ -39,8 +35,7 @@ DataURI = NamedTuple('DataURI', [('mimetype', str),
|
||||
('data', bytes)])
|
||||
|
||||
|
||||
def get_image_size(filename):
|
||||
# type: (str) -> Tuple[int, int]
|
||||
def get_image_size(filename: str) -> Tuple[int, int]:
|
||||
try:
|
||||
size = imagesize.get(filename)
|
||||
if size[0] == -1:
|
||||
@ -59,8 +54,7 @@ def get_image_size(filename):
|
||||
return None
|
||||
|
||||
|
||||
def guess_mimetype_for_stream(stream, default=None):
|
||||
# type: (IO, str) -> str
|
||||
def guess_mimetype_for_stream(stream: IO, default: str = None) -> str:
|
||||
imgtype = imghdr.what(stream) # type: ignore
|
||||
if imgtype:
|
||||
return 'image/' + imgtype
|
||||
@ -68,8 +62,7 @@ def guess_mimetype_for_stream(stream, default=None):
|
||||
return default
|
||||
|
||||
|
||||
def guess_mimetype(filename, default=None):
|
||||
# type: (str, str) -> str
|
||||
def guess_mimetype(filename: str = '', default: str = None) -> str:
|
||||
_, ext = path.splitext(filename.lower())
|
||||
if ext in mime_suffixes:
|
||||
return mime_suffixes[ext]
|
||||
@ -80,8 +73,7 @@ def guess_mimetype(filename, default=None):
|
||||
return default
|
||||
|
||||
|
||||
def get_image_extension(mimetype):
|
||||
# type: (str) -> str
|
||||
def get_image_extension(mimetype: str) -> str:
|
||||
for ext, _mimetype in mime_suffixes.items():
|
||||
if mimetype == _mimetype:
|
||||
return ext
|
||||
@ -89,8 +81,7 @@ def get_image_extension(mimetype):
|
||||
return None
|
||||
|
||||
|
||||
def parse_data_uri(uri):
|
||||
# type: (str) -> DataURI
|
||||
def parse_data_uri(uri: str) -> DataURI:
|
||||
if not uri.startswith('data:'):
|
||||
return None
|
||||
|
||||
@ -111,8 +102,7 @@ def parse_data_uri(uri):
|
||||
return DataURI(mimetype, charset, image_data)
|
||||
|
||||
|
||||
def test_svg(h, f):
|
||||
# type: (bytes, IO) -> str
|
||||
def test_svg(h: bytes, f: IO) -> str:
|
||||
"""An additional imghdr library helper; test the header is SVG's or not."""
|
||||
try:
|
||||
if '<svg' in h.decode().lower():
|
||||
|
@ -19,14 +19,11 @@ from inspect import ( # NOQA
|
||||
isclass, ismethod, ismethoddescriptor, isroutine
|
||||
)
|
||||
from io import StringIO
|
||||
from typing import Any, Callable, Mapping, List, Tuple
|
||||
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.typing import NoneType
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Mapping, List, Tuple, Type # NOQA
|
||||
|
||||
if sys.version_info > (3, 7):
|
||||
from types import (
|
||||
ClassMethodDescriptorType,
|
||||
@ -112,26 +109,22 @@ def getargspec(func):
|
||||
kwonlyargs, kwdefaults, annotations)
|
||||
|
||||
|
||||
def isenumclass(x):
|
||||
# type: (Type) -> bool
|
||||
def isenumclass(x: Any) -> bool:
|
||||
"""Check if the object is subclass of enum."""
|
||||
return inspect.isclass(x) and issubclass(x, enum.Enum)
|
||||
|
||||
|
||||
def isenumattribute(x):
|
||||
# type: (Any) -> bool
|
||||
def isenumattribute(x: Any) -> bool:
|
||||
"""Check if the object is attribute of enum."""
|
||||
return isinstance(x, enum.Enum)
|
||||
|
||||
|
||||
def ispartial(obj):
|
||||
# type: (Any) -> bool
|
||||
def ispartial(obj: Any) -> bool:
|
||||
"""Check if the object is partial."""
|
||||
return isinstance(obj, (partial, partialmethod))
|
||||
|
||||
|
||||
def isclassmethod(obj):
|
||||
# type: (Any) -> bool
|
||||
def isclassmethod(obj: Any) -> bool:
|
||||
"""Check if the object is classmethod."""
|
||||
if isinstance(obj, classmethod):
|
||||
return True
|
||||
@ -141,8 +134,7 @@ def isclassmethod(obj):
|
||||
return False
|
||||
|
||||
|
||||
def isstaticmethod(obj, cls=None, name=None):
|
||||
# type: (Any, Any, str) -> bool
|
||||
def isstaticmethod(obj: Any, cls: Any = None, name: str = None) -> bool:
|
||||
"""Check if the object is staticmethod."""
|
||||
if isinstance(obj, staticmethod):
|
||||
return True
|
||||
@ -161,8 +153,7 @@ def isstaticmethod(obj, cls=None, name=None):
|
||||
return False
|
||||
|
||||
|
||||
def isdescriptor(x):
|
||||
# type: (Any) -> bool
|
||||
def isdescriptor(x: Any) -> bool:
|
||||
"""Check if the object is some kind of descriptor."""
|
||||
for item in '__get__', '__set__', '__delete__':
|
||||
if hasattr(safe_getattr(x, item, None), '__call__'):
|
||||
@ -170,14 +161,12 @@ def isdescriptor(x):
|
||||
return False
|
||||
|
||||
|
||||
def isabstractmethod(obj):
|
||||
# type: (Any) -> bool
|
||||
def isabstractmethod(obj: Any) -> bool:
|
||||
"""Check if the object is an abstractmethod."""
|
||||
return safe_getattr(obj, '__isabstractmethod__', False) is True
|
||||
|
||||
|
||||
def isattributedescriptor(obj):
|
||||
# type: (Any) -> bool
|
||||
def isattributedescriptor(obj: Any) -> bool:
|
||||
"""Check if the object is an attribute like descriptor."""
|
||||
if inspect.isdatadescriptor(object):
|
||||
# data descriptor is kind of attribute
|
||||
@ -204,20 +193,17 @@ def isattributedescriptor(obj):
|
||||
return False
|
||||
|
||||
|
||||
def isfunction(obj):
|
||||
# type: (Any) -> bool
|
||||
def isfunction(obj: Any) -> bool:
|
||||
"""Check if the object is function."""
|
||||
return inspect.isfunction(obj) or ispartial(obj) and inspect.isfunction(obj.func)
|
||||
|
||||
|
||||
def isbuiltin(obj):
|
||||
# type: (Any) -> bool
|
||||
def isbuiltin(obj: Any) -> bool:
|
||||
"""Check if the object is builtin."""
|
||||
return inspect.isbuiltin(obj) or ispartial(obj) and inspect.isbuiltin(obj.func)
|
||||
|
||||
|
||||
def iscoroutinefunction(obj):
|
||||
# type: (Any) -> bool
|
||||
def iscoroutinefunction(obj: Any) -> bool:
|
||||
"""Check if the object is coroutine-function."""
|
||||
if inspect.iscoroutinefunction(obj):
|
||||
return True
|
||||
@ -228,14 +214,12 @@ def iscoroutinefunction(obj):
|
||||
return False
|
||||
|
||||
|
||||
def isproperty(obj):
|
||||
# type: (Any) -> bool
|
||||
def isproperty(obj: Any) -> bool:
|
||||
"""Check if the object is property."""
|
||||
return isinstance(obj, property)
|
||||
|
||||
|
||||
def safe_getattr(obj, name, *defargs):
|
||||
# type: (Any, str, Any) -> Any
|
||||
def safe_getattr(obj: Any, name: str, *defargs) -> Any:
|
||||
"""A getattr() that turns all exceptions into AttributeErrors."""
|
||||
try:
|
||||
return getattr(obj, name, *defargs)
|
||||
@ -257,8 +241,8 @@ def safe_getattr(obj, name, *defargs):
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
def safe_getmembers(object, predicate=None, attr_getter=safe_getattr):
|
||||
# type: (Any, Callable[[str], bool], Callable) -> List[Tuple[str, Any]]
|
||||
def safe_getmembers(object: Any, predicate: Callable[[str], bool] = None,
|
||||
attr_getter: Callable = safe_getattr) -> List[Tuple[str, Any]]:
|
||||
"""A version of inspect.getmembers() that uses safe_getattr()."""
|
||||
results = [] # type: List[Tuple[str, Any]]
|
||||
for key in dir(object):
|
||||
@ -272,8 +256,7 @@ def safe_getmembers(object, predicate=None, attr_getter=safe_getattr):
|
||||
return results
|
||||
|
||||
|
||||
def object_description(object):
|
||||
# type: (Any) -> str
|
||||
def object_description(object: Any) -> str:
|
||||
"""A repr() implementation that returns text safe to use in reST context."""
|
||||
if isinstance(object, dict):
|
||||
try:
|
||||
@ -310,8 +293,7 @@ def object_description(object):
|
||||
return s.replace('\n', ' ')
|
||||
|
||||
|
||||
def is_builtin_class_method(obj, attr_name):
|
||||
# type: (Any, str) -> bool
|
||||
def is_builtin_class_method(obj: Any, attr_name: str) -> bool:
|
||||
"""If attr_name is implemented at builtin class, return True.
|
||||
|
||||
>>> is_builtin_class_method(int, '__init__')
|
||||
@ -333,8 +315,8 @@ class Signature:
|
||||
its return annotation.
|
||||
"""
|
||||
|
||||
def __init__(self, subject, bound_method=False, has_retval=True):
|
||||
# type: (Callable, bool, bool) -> None
|
||||
def __init__(self, subject: Callable, bound_method: bool = False,
|
||||
has_retval: bool = True) -> None:
|
||||
# check subject is not a built-in class (ex. int, str)
|
||||
if (isinstance(subject, type) and
|
||||
is_builtin_class_method(subject, "__new__") and
|
||||
@ -379,16 +361,14 @@ class Signature:
|
||||
self.skip_first_argument = False
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
# type: () -> Mapping
|
||||
def parameters(self) -> Mapping:
|
||||
if self.partialmethod_with_noargs:
|
||||
return {}
|
||||
else:
|
||||
return self.signature.parameters
|
||||
|
||||
@property
|
||||
def return_annotation(self):
|
||||
# type: () -> Any
|
||||
def return_annotation(self) -> Any:
|
||||
if self.signature:
|
||||
if self.has_retval:
|
||||
return self.signature.return_annotation
|
||||
@ -397,8 +377,7 @@ class Signature:
|
||||
else:
|
||||
return None
|
||||
|
||||
def format_args(self, show_annotation=True):
|
||||
# type: (bool) -> str
|
||||
def format_args(self, show_annotation: bool = True) -> str:
|
||||
args = []
|
||||
last_kind = None
|
||||
for i, param in enumerate(self.parameters.values()):
|
||||
@ -453,8 +432,7 @@ class Signature:
|
||||
|
||||
return '(%s) -> %s' % (', '.join(args), annotation)
|
||||
|
||||
def format_annotation(self, annotation):
|
||||
# type: (Any) -> str
|
||||
def format_annotation(self, annotation: Any) -> str:
|
||||
"""Return formatted representation of a type annotation.
|
||||
|
||||
Show qualified names for types and additional details for types from
|
||||
@ -480,8 +458,7 @@ class Signature:
|
||||
else:
|
||||
return self.format_annotation_old(annotation)
|
||||
|
||||
def format_annotation_new(self, annotation):
|
||||
# type: (Any) -> str
|
||||
def format_annotation_new(self, annotation: Any) -> str:
|
||||
"""format_annotation() for py37+"""
|
||||
module = getattr(annotation, '__module__', None)
|
||||
if module == 'typing':
|
||||
@ -517,8 +494,7 @@ class Signature:
|
||||
|
||||
return qualname
|
||||
|
||||
def format_annotation_old(self, annotation):
|
||||
# type: (Any) -> str
|
||||
def format_annotation_old(self, annotation: Any) -> str:
|
||||
"""format_annotation() for py36 or below"""
|
||||
module = getattr(annotation, '__module__', None)
|
||||
if module == 'typing':
|
||||
@ -619,8 +595,8 @@ class Signature:
|
||||
return qualname
|
||||
|
||||
|
||||
def getdoc(obj, attrgetter=safe_getattr, allow_inherited=False):
|
||||
# type: (Any, Callable, bool) -> str
|
||||
def getdoc(obj: Any, attrgetter: Callable = safe_getattr,
|
||||
allow_inherited: bool = False) -> str:
|
||||
"""Get the docstring for the object.
|
||||
|
||||
This tries to obtain the docstring for some kind of objects additionally:
|
||||
|
@ -10,20 +10,20 @@
|
||||
import os
|
||||
import re
|
||||
import zlib
|
||||
from typing import Callable, IO, Iterator
|
||||
|
||||
from sphinx.util import logging
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Callable, IO, Iterator # NOQA
|
||||
from sphinx.builders import Builder # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
from sphinx.util.typing import Inventory # NOQA
|
||||
from sphinx.util.typing import Inventory
|
||||
|
||||
|
||||
BUFSIZE = 16 * 1024
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.environment import BuildEnvironment
|
||||
|
||||
|
||||
class InventoryFileReader:
|
||||
"""A file reader for inventory file.
|
||||
@ -31,21 +31,18 @@ class InventoryFileReader:
|
||||
This reader supports mixture of texts and compressed texts.
|
||||
"""
|
||||
|
||||
def __init__(self, stream):
|
||||
# type: (IO) -> None
|
||||
def __init__(self, stream: IO) -> None:
|
||||
self.stream = stream
|
||||
self.buffer = b''
|
||||
self.eof = False
|
||||
|
||||
def read_buffer(self):
|
||||
# type: () -> None
|
||||
def read_buffer(self) -> None:
|
||||
chunk = self.stream.read(BUFSIZE)
|
||||
if chunk == b'':
|
||||
self.eof = True
|
||||
self.buffer += chunk
|
||||
|
||||
def readline(self):
|
||||
# type: () -> str
|
||||
def readline(self) -> str:
|
||||
pos = self.buffer.find(b'\n')
|
||||
if pos != -1:
|
||||
line = self.buffer[:pos].decode()
|
||||
@ -59,15 +56,13 @@ class InventoryFileReader:
|
||||
|
||||
return line
|
||||
|
||||
def readlines(self):
|
||||
# type: () -> Iterator[str]
|
||||
def readlines(self) -> Iterator[str]:
|
||||
while not self.eof:
|
||||
line = self.readline()
|
||||
if line:
|
||||
yield line
|
||||
|
||||
def read_compressed_chunks(self):
|
||||
# type: () -> Iterator[bytes]
|
||||
def read_compressed_chunks(self) -> Iterator[bytes]:
|
||||
decompressor = zlib.decompressobj()
|
||||
while not self.eof:
|
||||
self.read_buffer()
|
||||
@ -75,8 +70,7 @@ class InventoryFileReader:
|
||||
self.buffer = b''
|
||||
yield decompressor.flush()
|
||||
|
||||
def read_compressed_lines(self):
|
||||
# type: () -> Iterator[str]
|
||||
def read_compressed_lines(self) -> Iterator[str]:
|
||||
buf = b''
|
||||
for chunk in self.read_compressed_chunks():
|
||||
buf += chunk
|
||||
@ -89,8 +83,7 @@ class InventoryFileReader:
|
||||
|
||||
class InventoryFile:
|
||||
@classmethod
|
||||
def load(cls, stream, uri, joinfunc):
|
||||
# type: (IO, str, Callable) -> Inventory
|
||||
def load(cls, stream: IO, uri: str, joinfunc: Callable) -> Inventory:
|
||||
reader = InventoryFileReader(stream)
|
||||
line = reader.readline().rstrip()
|
||||
if line == '# Sphinx inventory version 1':
|
||||
@ -101,8 +94,7 @@ class InventoryFile:
|
||||
raise ValueError('invalid inventory header: %s' % line)
|
||||
|
||||
@classmethod
|
||||
def load_v1(cls, stream, uri, join):
|
||||
# type: (InventoryFileReader, str, Callable) -> Inventory
|
||||
def load_v1(cls, stream: InventoryFileReader, uri: str, join: Callable) -> Inventory:
|
||||
invdata = {} # type: Inventory
|
||||
projname = stream.readline().rstrip()[11:]
|
||||
version = stream.readline().rstrip()[11:]
|
||||
@ -120,8 +112,7 @@ class InventoryFile:
|
||||
return invdata
|
||||
|
||||
@classmethod
|
||||
def load_v2(cls, stream, uri, join):
|
||||
# type: (InventoryFileReader, str, Callable) -> Inventory
|
||||
def load_v2(cls, stream: InventoryFileReader, uri: str, join: Callable) -> Inventory:
|
||||
invdata = {} # type: Inventory
|
||||
projname = stream.readline().rstrip()[11:]
|
||||
version = stream.readline().rstrip()[11:]
|
||||
@ -150,10 +141,8 @@ class InventoryFile:
|
||||
return invdata
|
||||
|
||||
@classmethod
|
||||
def dump(cls, filename, env, builder):
|
||||
# type: (str, BuildEnvironment, Builder) -> None
|
||||
def escape(string):
|
||||
# type: (str) -> str
|
||||
def dump(cls, filename: str, env: "BuildEnvironment", builder: "Builder") -> None:
|
||||
def escape(string: str) -> str:
|
||||
return re.sub("\\s+", " ", string)
|
||||
|
||||
with open(os.path.join(filename), 'wb') as f:
|
||||
|
@ -10,10 +10,7 @@
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Dict, IO, List, Match, Union # NOQA
|
||||
from typing import Any, Dict, IO, List, Match, Union
|
||||
|
||||
_str_re = re.compile(r'"(\\\\|\\"|[^"])*"')
|
||||
_int_re = re.compile(r'\d+')
|
||||
@ -35,10 +32,8 @@ ESCAPE_DICT = {
|
||||
ESCAPED = re.compile(r'\\u.{4}|\\.')
|
||||
|
||||
|
||||
def encode_string(s):
|
||||
# type: (str) -> str
|
||||
def replace(match):
|
||||
# type: (Match) -> str
|
||||
def encode_string(s: str) -> str:
|
||||
def replace(match: Match) -> str:
|
||||
s = match.group(0)
|
||||
try:
|
||||
return ESCAPE_DICT[s]
|
||||
@ -55,8 +50,7 @@ def encode_string(s):
|
||||
return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
|
||||
|
||||
|
||||
def decode_string(s):
|
||||
# type: (str) -> str
|
||||
def decode_string(s: str) -> str:
|
||||
return ESCAPED.sub(lambda m: eval('"' + m.group() + '"'), s)
|
||||
|
||||
|
||||
@ -78,8 +72,7 @@ do import static with
|
||||
double in super""".split())
|
||||
|
||||
|
||||
def dumps(obj, key=False):
|
||||
# type: (Any, bool) -> str
|
||||
def dumps(obj: Any, key: bool = False) -> str:
|
||||
if key:
|
||||
if not isinstance(obj, str):
|
||||
obj = str(obj)
|
||||
@ -107,13 +100,11 @@ def dumps(obj, key=False):
|
||||
raise TypeError(type(obj))
|
||||
|
||||
|
||||
def dump(obj, f):
|
||||
# type: (Any, IO) -> None
|
||||
def dump(obj: Any, f: IO) -> None:
|
||||
f.write(dumps(obj))
|
||||
|
||||
|
||||
def loads(x):
|
||||
# type: (str) -> Any
|
||||
def loads(x: str) -> Any:
|
||||
"""Loader that can read the JS subset the indexer produces."""
|
||||
nothing = object()
|
||||
i = 0
|
||||
@ -205,6 +196,5 @@ def loads(x):
|
||||
return obj
|
||||
|
||||
|
||||
def load(f):
|
||||
# type: (IO) -> Any
|
||||
def load(f: IO) -> Any:
|
||||
return loads(f.read())
|
||||
|
@ -11,13 +11,10 @@
|
||||
import json
|
||||
import warnings
|
||||
from collections import UserString
|
||||
from typing import Any, IO
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, IO # NOQA
|
||||
|
||||
|
||||
warnings.warn('sphinx.util.jsonimpl is deprecated',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
@ -25,30 +22,25 @@ warnings.warn('sphinx.util.jsonimpl is deprecated',
|
||||
|
||||
class SphinxJSONEncoder(json.JSONEncoder):
|
||||
"""JSONEncoder subclass that forces translation proxies."""
|
||||
def default(self, obj):
|
||||
# type: (Any) -> str
|
||||
def default(self, obj: Any) -> str:
|
||||
if isinstance(obj, UserString):
|
||||
return str(obj)
|
||||
return super().default(obj)
|
||||
|
||||
|
||||
def dump(obj, fp, *args, **kwds):
|
||||
# type: (Any, IO, Any, Any) -> None
|
||||
def dump(obj: Any, fp: IO, *args, **kwds) -> None:
|
||||
kwds['cls'] = SphinxJSONEncoder
|
||||
json.dump(obj, fp, *args, **kwds)
|
||||
|
||||
|
||||
def dumps(obj, *args, **kwds):
|
||||
# type: (Any, Any, Any) -> str
|
||||
def dumps(obj: Any, *args, **kwds) -> str:
|
||||
kwds['cls'] = SphinxJSONEncoder
|
||||
return json.dumps(obj, *args, **kwds)
|
||||
|
||||
|
||||
def load(*args, **kwds):
|
||||
# type: (Any, Any) -> Any
|
||||
def load(*args, **kwds) -> Any:
|
||||
return json.load(*args, **kwds)
|
||||
|
||||
|
||||
def loads(*args, **kwds):
|
||||
# type: (Any, Any) -> Any
|
||||
def loads(*args, **kwds) -> Any:
|
||||
return json.loads(*args, **kwds)
|
||||
|
@ -12,8 +12,10 @@ import logging
|
||||
import logging.handlers
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Dict, Generator, IO, List, Tuple, Type, Union
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Node
|
||||
from docutils.utils import get_source_line
|
||||
|
||||
from sphinx.errors import SphinxWarning
|
||||
@ -21,8 +23,7 @@ from sphinx.util.console import colorize
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Dict, Generator, IO, List, Tuple, Type, Union # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.application import Sphinx
|
||||
|
||||
|
||||
NAMESPACE = 'sphinx'
|
||||
@ -54,8 +55,7 @@ COLOR_MAP = defaultdict(lambda: 'blue',
|
||||
})
|
||||
|
||||
|
||||
def getLogger(name):
|
||||
# type: (str) -> SphinxLoggerAdapter
|
||||
def getLogger(name: str) -> "SphinxLoggerAdapter":
|
||||
"""Get logger wrapped by :class:`sphinx.util.logging.SphinxLoggerAdapter`.
|
||||
|
||||
Sphinx logger always uses ``sphinx.*`` namespace to be independent from
|
||||
@ -77,8 +77,7 @@ def getLogger(name):
|
||||
return SphinxLoggerAdapter(logger, {})
|
||||
|
||||
|
||||
def convert_serializable(records):
|
||||
# type: (List[logging.LogRecord]) -> None
|
||||
def convert_serializable(records: List[logging.LogRecord]) -> None:
|
||||
"""Convert LogRecord serializable."""
|
||||
for r in records:
|
||||
# extract arguments to a message and clear them
|
||||
@ -95,8 +94,7 @@ class SphinxLogRecord(logging.LogRecord):
|
||||
prefix = ''
|
||||
location = None # type: Any
|
||||
|
||||
def getMessage(self):
|
||||
# type: () -> str
|
||||
def getMessage(self) -> str:
|
||||
message = super().getMessage()
|
||||
location = getattr(self, 'location', None)
|
||||
if location:
|
||||
@ -120,20 +118,17 @@ class SphinxWarningLogRecord(SphinxLogRecord):
|
||||
class SphinxLoggerAdapter(logging.LoggerAdapter):
|
||||
"""LoggerAdapter allowing ``type`` and ``subtype`` keywords."""
|
||||
|
||||
def log(self, level, msg, *args, **kwargs):
|
||||
# type: (Union[int, str], str, Any, Any) -> None
|
||||
def log(self, level: Union[int, str], msg: str, *args, **kwargs) -> None:
|
||||
if isinstance(level, int):
|
||||
super().log(level, msg, *args, **kwargs)
|
||||
else:
|
||||
levelno = LEVEL_NAMES[level]
|
||||
super().log(levelno, msg, *args, **kwargs)
|
||||
|
||||
def verbose(self, msg, *args, **kwargs):
|
||||
# type: (str, Any, Any) -> None
|
||||
def verbose(self, msg: str, *args, **kwargs) -> None:
|
||||
self.log(VERBOSE, msg, *args, **kwargs)
|
||||
|
||||
def process(self, msg, kwargs): # type: ignore
|
||||
# type: (str, Dict) -> Tuple[str, Dict]
|
||||
def process(self, msg: str, kwargs: Dict) -> Tuple[str, Dict]: # type: ignore
|
||||
extra = kwargs.setdefault('extra', {})
|
||||
if 'type' in kwargs:
|
||||
extra['type'] = kwargs.pop('type')
|
||||
@ -148,8 +143,7 @@ class SphinxLoggerAdapter(logging.LoggerAdapter):
|
||||
|
||||
return msg, kwargs
|
||||
|
||||
def handle(self, record):
|
||||
# type: (logging.LogRecord) -> None
|
||||
def handle(self, record: logging.LogRecord) -> None:
|
||||
self.logger.handle(record)
|
||||
|
||||
|
||||
@ -161,8 +155,7 @@ class WarningStreamHandler(logging.StreamHandler):
|
||||
class NewLineStreamHandler(logging.StreamHandler):
|
||||
"""StreamHandler which switches line terminator by record.nonl flag."""
|
||||
|
||||
def emit(self, record):
|
||||
# type: (logging.LogRecord) -> None
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
try:
|
||||
self.acquire()
|
||||
if getattr(record, 'nonl', False):
|
||||
@ -177,16 +170,13 @@ class NewLineStreamHandler(logging.StreamHandler):
|
||||
class MemoryHandler(logging.handlers.BufferingHandler):
|
||||
"""Handler buffering all logs."""
|
||||
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
def __init__(self) -> None:
|
||||
super().__init__(-1)
|
||||
|
||||
def shouldFlush(self, record):
|
||||
# type: (logging.LogRecord) -> bool
|
||||
def shouldFlush(self, record: logging.LogRecord) -> bool:
|
||||
return False # never flush
|
||||
|
||||
def flushTo(self, logger):
|
||||
# type: (logging.Logger) -> None
|
||||
def flushTo(self, logger: logging.Logger) -> None:
|
||||
self.acquire()
|
||||
try:
|
||||
for record in self.buffer:
|
||||
@ -195,15 +185,13 @@ class MemoryHandler(logging.handlers.BufferingHandler):
|
||||
finally:
|
||||
self.release()
|
||||
|
||||
def clear(self):
|
||||
# type: () -> List[logging.LogRecord]
|
||||
def clear(self) -> List[logging.LogRecord]:
|
||||
buffer, self.buffer = self.buffer, []
|
||||
return buffer
|
||||
|
||||
|
||||
@contextmanager
|
||||
def pending_warnings():
|
||||
# type: () -> Generator
|
||||
def pending_warnings() -> Generator[logging.Handler, None, None]:
|
||||
"""Contextmanager to pend logging warnings temporary.
|
||||
|
||||
Similar to :func:`pending_logging`.
|
||||
@ -231,8 +219,7 @@ def pending_warnings():
|
||||
|
||||
|
||||
@contextmanager
|
||||
def pending_logging():
|
||||
# type: () -> Generator
|
||||
def pending_logging() -> Generator[MemoryHandler, None, None]:
|
||||
"""Contextmanager to pend logging all logs temporary.
|
||||
|
||||
For example::
|
||||
@ -264,8 +251,7 @@ def pending_logging():
|
||||
|
||||
|
||||
@contextmanager
|
||||
def skip_warningiserror(skip=True):
|
||||
# type: (bool) -> Generator
|
||||
def skip_warningiserror(skip: bool = True) -> Generator[None, None, None]:
|
||||
"""contextmanager to skip WarningIsErrorFilter for a while."""
|
||||
logger = logging.getLogger(NAMESPACE)
|
||||
|
||||
@ -285,8 +271,7 @@ def skip_warningiserror(skip=True):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def prefixed_warnings(prefix):
|
||||
# type: (str) -> Generator
|
||||
def prefixed_warnings(prefix: str) -> Generator[None, None, None]:
|
||||
"""Prepend prefix to all records for a while.
|
||||
|
||||
For example::
|
||||
@ -332,13 +317,11 @@ def prefixed_warnings(prefix):
|
||||
|
||||
|
||||
class LogCollector:
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
def __init__(self) -> None:
|
||||
self.logs = [] # type: List[logging.LogRecord]
|
||||
|
||||
@contextmanager
|
||||
def collect(self):
|
||||
# type: () -> Generator
|
||||
def collect(self) -> Generator[None, None, None]:
|
||||
with pending_logging() as memhandler:
|
||||
yield
|
||||
|
||||
@ -348,16 +331,14 @@ class LogCollector:
|
||||
class InfoFilter(logging.Filter):
|
||||
"""Filter error and warning messages."""
|
||||
|
||||
def filter(self, record):
|
||||
# type: (logging.LogRecord) -> bool
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
if record.levelno < logging.WARNING:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def is_suppressed_warning(type, subtype, suppress_warnings):
|
||||
# type: (str, str, List[str]) -> bool
|
||||
def is_suppressed_warning(type: str, subtype: str, suppress_warnings: List[str]) -> bool:
|
||||
"""Check the warning is suppressed or not."""
|
||||
if type is None:
|
||||
return False
|
||||
@ -379,13 +360,11 @@ def is_suppressed_warning(type, subtype, suppress_warnings):
|
||||
class WarningSuppressor(logging.Filter):
|
||||
"""Filter logs by `suppress_warnings`."""
|
||||
|
||||
def __init__(self, app):
|
||||
# type: (Sphinx) -> None
|
||||
def __init__(self, app: "Sphinx") -> None:
|
||||
self.app = app
|
||||
super().__init__()
|
||||
|
||||
def filter(self, record):
|
||||
# type: (logging.LogRecord) -> bool
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
type = getattr(record, 'type', None)
|
||||
subtype = getattr(record, 'subtype', None)
|
||||
|
||||
@ -405,13 +384,11 @@ class WarningSuppressor(logging.Filter):
|
||||
class WarningIsErrorFilter(logging.Filter):
|
||||
"""Raise exception if warning emitted."""
|
||||
|
||||
def __init__(self, app):
|
||||
# type: (Sphinx) -> None
|
||||
def __init__(self, app: "Sphinx") -> None:
|
||||
self.app = app
|
||||
super().__init__()
|
||||
|
||||
def filter(self, record):
|
||||
# type: (logging.LogRecord) -> bool
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
if getattr(record, 'skip_warningsiserror', False):
|
||||
# disabled by DisableWarningIsErrorFilter
|
||||
return True
|
||||
@ -433,8 +410,7 @@ class WarningIsErrorFilter(logging.Filter):
|
||||
class DisableWarningIsErrorFilter(logging.Filter):
|
||||
"""Disable WarningIsErrorFilter if this filter installed."""
|
||||
|
||||
def filter(self, record):
|
||||
# type: (logging.LogRecord) -> bool
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
record.skip_warningsiserror = True # type: ignore
|
||||
return True
|
||||
|
||||
@ -442,13 +418,11 @@ class DisableWarningIsErrorFilter(logging.Filter):
|
||||
class MessagePrefixFilter(logging.Filter):
|
||||
"""Prepend prefix to all records."""
|
||||
|
||||
def __init__(self, prefix):
|
||||
# type: (str) -> None
|
||||
def __init__(self, prefix: str) -> None:
|
||||
self.prefix = prefix
|
||||
super().__init__()
|
||||
|
||||
def filter(self, record):
|
||||
# type: (logging.LogRecord) -> bool
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
if self.prefix:
|
||||
record.msg = self.prefix + ' ' + record.msg
|
||||
return True
|
||||
@ -462,13 +436,11 @@ class SphinxLogRecordTranslator(logging.Filter):
|
||||
"""
|
||||
LogRecordClass = None # type: Type[logging.LogRecord]
|
||||
|
||||
def __init__(self, app):
|
||||
# type: (Sphinx) -> None
|
||||
def __init__(self, app: "Sphinx") -> None:
|
||||
self.app = app
|
||||
super().__init__()
|
||||
|
||||
def filter(self, record): # type: ignore
|
||||
# type: (SphinxWarningLogRecord) -> bool
|
||||
def filter(self, record: SphinxWarningLogRecord) -> bool: # type: ignore
|
||||
if isinstance(record, logging.LogRecord):
|
||||
# force subclassing to handle location
|
||||
record.__class__ = self.LogRecordClass # type: ignore
|
||||
@ -500,8 +472,7 @@ class WarningLogRecordTranslator(SphinxLogRecordTranslator):
|
||||
LogRecordClass = SphinxWarningLogRecord
|
||||
|
||||
|
||||
def get_node_location(node):
|
||||
# type: (nodes.Node) -> str
|
||||
def get_node_location(node: Node) -> str:
|
||||
(source, line) = get_source_line(node)
|
||||
if source and line:
|
||||
return "%s:%s" % (source, line)
|
||||
@ -514,8 +485,7 @@ def get_node_location(node):
|
||||
|
||||
|
||||
class ColorizeFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
# type: (logging.LogRecord) -> str
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
message = super().format(record)
|
||||
color = getattr(record, 'color', None)
|
||||
if color is None:
|
||||
@ -529,13 +499,11 @@ class ColorizeFormatter(logging.Formatter):
|
||||
|
||||
class SafeEncodingWriter:
|
||||
"""Stream writer which ignores UnicodeEncodeError silently"""
|
||||
def __init__(self, stream):
|
||||
# type: (IO) -> None
|
||||
def __init__(self, stream: IO) -> None:
|
||||
self.stream = stream
|
||||
self.encoding = getattr(stream, 'encoding', 'ascii') or 'ascii'
|
||||
|
||||
def write(self, data):
|
||||
# type: (str) -> None
|
||||
def write(self, data: str) -> None:
|
||||
try:
|
||||
self.stream.write(data)
|
||||
except UnicodeEncodeError:
|
||||
@ -543,25 +511,21 @@ class SafeEncodingWriter:
|
||||
# non-encodable characters, then decode them.
|
||||
self.stream.write(data.encode(self.encoding, 'replace').decode(self.encoding))
|
||||
|
||||
def flush(self):
|
||||
# type: () -> None
|
||||
def flush(self) -> None:
|
||||
if hasattr(self.stream, 'flush'):
|
||||
self.stream.flush()
|
||||
|
||||
|
||||
class LastMessagesWriter:
|
||||
"""Stream writer which memories last 10 messages to save trackback"""
|
||||
def __init__(self, app, stream):
|
||||
# type: (Sphinx, IO) -> None
|
||||
def __init__(self, app: "Sphinx", stream: IO) -> None:
|
||||
self.app = app
|
||||
|
||||
def write(self, data):
|
||||
# type: (str) -> None
|
||||
def write(self, data: str) -> None:
|
||||
self.app.messagelog.append(data)
|
||||
|
||||
|
||||
def setup(app, status, warning):
|
||||
# type: (Sphinx, IO, IO) -> None
|
||||
def setup(app: "Sphinx", status: IO, warning: IO) -> None:
|
||||
"""Setup root logger for Sphinx"""
|
||||
logger = logging.getLogger(NAMESPACE)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
@ -9,16 +9,12 @@
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Callable, Dict, List, Match, Pattern
|
||||
|
||||
from sphinx.util.osutil import canon_path
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Callable, Dict, List, Match, Pattern # NOQA
|
||||
|
||||
|
||||
def _translate_pattern(pat):
|
||||
# type: (str) -> str
|
||||
def _translate_pattern(pat: str) -> str:
|
||||
"""Translate a shell-style glob pattern to a regular expression.
|
||||
|
||||
Adapted from the fnmatch module, but enhanced so that single stars don't
|
||||
@ -64,8 +60,7 @@ def _translate_pattern(pat):
|
||||
return res + '$'
|
||||
|
||||
|
||||
def compile_matchers(patterns):
|
||||
# type: (List[str]) -> List[Callable[[str], Match[str]]]
|
||||
def compile_matchers(patterns: List[str]) -> List[Callable[[str], Match[str]]]:
|
||||
return [re.compile(_translate_pattern(pat)).match for pat in patterns]
|
||||
|
||||
|
||||
@ -76,17 +71,14 @@ class Matcher:
|
||||
For example, "**/index.rst" matches with "index.rst"
|
||||
"""
|
||||
|
||||
def __init__(self, patterns):
|
||||
# type: (List[str]) -> None
|
||||
def __init__(self, patterns: List[str]) -> None:
|
||||
expanded = [pat[3:] for pat in patterns if pat.startswith('**/')]
|
||||
self.patterns = compile_matchers(patterns + expanded)
|
||||
|
||||
def __call__(self, string):
|
||||
# type: (str) -> bool
|
||||
def __call__(self, string: str) -> bool:
|
||||
return self.match(string)
|
||||
|
||||
def match(self, string):
|
||||
# type: (str) -> bool
|
||||
def match(self, string: str) -> bool:
|
||||
string = canon_path(string)
|
||||
return any(pat(string) for pat in self.patterns)
|
||||
|
||||
@ -97,16 +89,14 @@ DOTFILES = Matcher(['**/.*'])
|
||||
_pat_cache = {} # type: Dict[str, Pattern]
|
||||
|
||||
|
||||
def patmatch(name, pat):
|
||||
# type: (str, str) -> Match[str]
|
||||
def patmatch(name: str, pat: str) -> Match[str]:
|
||||
"""Return if name matches pat. Adapted from fnmatch module."""
|
||||
if pat not in _pat_cache:
|
||||
_pat_cache[pat] = re.compile(_translate_pattern(pat))
|
||||
return _pat_cache[pat].match(name)
|
||||
|
||||
|
||||
def patfilter(names, pat):
|
||||
# type: (List[str], str) -> List[str]
|
||||
def patfilter(names: List[str], pat: str) -> List[str]:
|
||||
"""Return the subset of the list NAMES that match PAT.
|
||||
|
||||
Adapted from fnmatch module.
|
||||
|
@ -8,15 +8,12 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from docutils import nodes # NOQA
|
||||
from sphinx.builders.html import HTMLTranslator # NOQA
|
||||
from sphinx.builders.html import HTMLTranslator
|
||||
|
||||
|
||||
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:
|
||||
if writer.builder.config.math_numfig and writer.builder.config.numfig:
|
||||
figtype = 'displaymath'
|
||||
if writer.builder.name == 'singlehtml':
|
||||
@ -31,10 +28,8 @@ def get_node_equation_number(writer, node):
|
||||
return node['number']
|
||||
|
||||
|
||||
def wrap_displaymath(text, label, numbering):
|
||||
# type: (str, str, bool) -> str
|
||||
def is_equation(part):
|
||||
# type: (str) -> str
|
||||
def wrap_displaymath(text: str, label: str, numbering: bool) -> str:
|
||||
def is_equation(part: str) -> str:
|
||||
return part.strip()
|
||||
|
||||
if label is None:
|
||||
|
@ -10,9 +10,14 @@
|
||||
|
||||
import re
|
||||
import warnings
|
||||
from typing import Any, cast
|
||||
from typing import Any, Callable, Iterable, List, Set, Tuple, Type
|
||||
from typing import cast
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Element, Node
|
||||
from docutils.parsers.rst import Directive
|
||||
from docutils.parsers.rst.states import Inliner
|
||||
from docutils.statemachine import StringList
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
@ -21,11 +26,8 @@ from sphinx.util import logging
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Callable, Iterable, List, Optional, Set, Tuple, Type # NOQA
|
||||
from docutils.parsers.rst.states import Inliner # NOQA
|
||||
from docutils.statemachine import StringList # NOQA
|
||||
from sphinx.builders import Builder # NOQA
|
||||
from sphinx.utils.tags import Tags # NOQA
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.utils.tags import Tags
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -57,13 +59,11 @@ class NodeMatcher:
|
||||
# => [<reference ...>, <reference ...>, ...]
|
||||
"""
|
||||
|
||||
def __init__(self, *classes, **attrs):
|
||||
# type: (Type[nodes.Node], Any) -> None
|
||||
def __init__(self, *classes: Type[Node], **attrs) -> None:
|
||||
self.classes = classes
|
||||
self.attrs = attrs
|
||||
|
||||
def match(self, node):
|
||||
# type: (nodes.Node) -> bool
|
||||
def match(self, node: Node) -> bool:
|
||||
try:
|
||||
if self.classes and not isinstance(node, self.classes):
|
||||
return False
|
||||
@ -85,13 +85,11 @@ class NodeMatcher:
|
||||
# for non-Element nodes
|
||||
return False
|
||||
|
||||
def __call__(self, node):
|
||||
# type: (nodes.Node) -> bool
|
||||
def __call__(self, node: Node) -> bool:
|
||||
return self.match(node)
|
||||
|
||||
|
||||
def get_full_module_name(node):
|
||||
# type: (nodes.Node) -> str
|
||||
def get_full_module_name(node: Node) -> str:
|
||||
"""
|
||||
return full module dotted path like: 'docutils.nodes.paragraph'
|
||||
|
||||
@ -101,8 +99,7 @@ def get_full_module_name(node):
|
||||
return '{}.{}'.format(node.__module__, node.__class__.__name__)
|
||||
|
||||
|
||||
def repr_domxml(node, length=80):
|
||||
# type: (nodes.Node, Optional[int]) -> str
|
||||
def repr_domxml(node: Node, length: int = 80) -> str:
|
||||
"""
|
||||
return DOM XML representation of the specified node like:
|
||||
'<paragraph translatable="False"><inline classes="versionmodified">New in version...'
|
||||
@ -122,8 +119,7 @@ def repr_domxml(node, length=80):
|
||||
return text
|
||||
|
||||
|
||||
def apply_source_workaround(node):
|
||||
# type: (nodes.Element) -> None
|
||||
def apply_source_workaround(node: Element) -> None:
|
||||
# workaround: nodes.term have wrong rawsource if classifier is specified.
|
||||
# The behavior of docutils-0.11, 0.12 is:
|
||||
# * when ``term text : classifier1 : classifier2`` is specified,
|
||||
@ -186,8 +182,7 @@ IGNORED_NODES = (
|
||||
)
|
||||
|
||||
|
||||
def is_pending_meta(node):
|
||||
# type: (nodes.Node) -> bool
|
||||
def is_pending_meta(node: Node) -> bool:
|
||||
if (isinstance(node, nodes.pending) and
|
||||
isinstance(node.details.get('nodes', [None])[0], addnodes.meta)):
|
||||
return True
|
||||
@ -195,8 +190,7 @@ def is_pending_meta(node):
|
||||
return False
|
||||
|
||||
|
||||
def is_translatable(node):
|
||||
# type: (nodes.Node) -> bool
|
||||
def is_translatable(node: Node) -> bool:
|
||||
if isinstance(node, addnodes.translatable):
|
||||
return True
|
||||
|
||||
@ -251,8 +245,7 @@ META_TYPE_NODES = (
|
||||
)
|
||||
|
||||
|
||||
def extract_messages(doctree):
|
||||
# type: (nodes.Element) -> Iterable[Tuple[nodes.Element, str]]
|
||||
def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]:
|
||||
"""Extract translatable messages from a document tree."""
|
||||
for node in doctree.traverse(is_translatable): # type: nodes.Element
|
||||
if isinstance(node, addnodes.translatable):
|
||||
@ -279,39 +272,34 @@ def extract_messages(doctree):
|
||||
yield node, msg
|
||||
|
||||
|
||||
def find_source_node(node):
|
||||
# type: (nodes.Element) -> str
|
||||
def find_source_node(node: Element) -> str:
|
||||
warnings.warn('find_source_node() is deprecated.',
|
||||
RemovedInSphinx40Warning)
|
||||
return get_node_source(node)
|
||||
|
||||
|
||||
def get_node_source(node):
|
||||
# type: (nodes.Element) -> str
|
||||
def get_node_source(node: Element) -> str:
|
||||
for pnode in traverse_parent(node):
|
||||
if pnode.source:
|
||||
return pnode.source
|
||||
return None
|
||||
|
||||
|
||||
def get_node_line(node):
|
||||
# type: (nodes.Element) -> int
|
||||
def get_node_line(node: Element) -> int:
|
||||
for pnode in traverse_parent(node):
|
||||
if pnode.line:
|
||||
return pnode.line
|
||||
return None
|
||||
|
||||
|
||||
def traverse_parent(node, cls=None):
|
||||
# type: (nodes.Element, Any) -> Iterable[nodes.Element]
|
||||
def traverse_parent(node: Element, cls: Any = None) -> Iterable[Element]:
|
||||
while node:
|
||||
if cls is None or isinstance(node, cls):
|
||||
yield node
|
||||
node = node.parent
|
||||
|
||||
|
||||
def get_prev_node(node):
|
||||
# type: (nodes.Node) -> nodes.Node
|
||||
def get_prev_node(node: Node) -> Node:
|
||||
pos = node.parent.index(node)
|
||||
if pos > 0:
|
||||
return node.parent[pos - 1]
|
||||
@ -319,8 +307,7 @@ def get_prev_node(node):
|
||||
return None
|
||||
|
||||
|
||||
def traverse_translatable_index(doctree):
|
||||
# type: (nodes.Element) -> Iterable[Tuple[nodes.Element, List[str]]]
|
||||
def traverse_translatable_index(doctree: Element) -> Iterable[Tuple[Element, List[str]]]:
|
||||
"""Traverse translatable index node from a document tree."""
|
||||
for node in doctree.traverse(NodeMatcher(addnodes.index, inline=False)): # type: addnodes.index # NOQA
|
||||
if 'raw_entries' in node:
|
||||
@ -330,8 +317,7 @@ def traverse_translatable_index(doctree):
|
||||
yield node, entries
|
||||
|
||||
|
||||
def nested_parse_with_titles(state, content, node):
|
||||
# type: (Any, StringList, nodes.Node) -> str
|
||||
def nested_parse_with_titles(state: Any, content: StringList, node: Node) -> str:
|
||||
"""Version of state.nested_parse() that allows titles and does not require
|
||||
titles to have the same decoration as the calling document.
|
||||
|
||||
@ -350,8 +336,7 @@ def nested_parse_with_titles(state, content, node):
|
||||
state.memo.section_level = surrounding_section_level
|
||||
|
||||
|
||||
def clean_astext(node):
|
||||
# type: (nodes.Element) -> str
|
||||
def clean_astext(node: Element) -> str:
|
||||
"""Like node.astext(), but ignore images."""
|
||||
node = node.deepcopy()
|
||||
for img in node.traverse(nodes.image):
|
||||
@ -361,8 +346,7 @@ def clean_astext(node):
|
||||
return node.astext()
|
||||
|
||||
|
||||
def split_explicit_title(text):
|
||||
# type: (str) -> Tuple[bool, str, str]
|
||||
def split_explicit_title(text: str) -> Tuple[bool, str, str]:
|
||||
"""Split role content into title and target, if given."""
|
||||
match = explicit_title_re.match(text)
|
||||
if match:
|
||||
@ -375,8 +359,7 @@ indextypes = [
|
||||
]
|
||||
|
||||
|
||||
def process_index_entry(entry, targetid):
|
||||
# type: (str, str) -> List[Tuple[str, str, str, str, str]]
|
||||
def process_index_entry(entry: str, targetid: str) -> List[Tuple[str, str, str, str, str]]:
|
||||
from sphinx.domains.python import pairindextypes
|
||||
|
||||
indexentries = [] # type: List[Tuple[str, str, str, str, str]]
|
||||
@ -414,8 +397,9 @@ def process_index_entry(entry, targetid):
|
||||
return indexentries
|
||||
|
||||
|
||||
def inline_all_toctrees(builder, docnameset, docname, tree, colorfunc, traversed):
|
||||
# type: (Builder, Set[str], str, nodes.document, Callable, List[str]) -> nodes.document
|
||||
def inline_all_toctrees(builder: "Builder", docnameset: Set[str], docname: str,
|
||||
tree: nodes.document, colorfunc: Callable, traversed: List[str]
|
||||
) -> nodes.document:
|
||||
"""Inline all toctrees in the *tree*.
|
||||
|
||||
Record all docnames in *docnameset*, and output docnames with *colorfunc*.
|
||||
@ -447,8 +431,8 @@ def inline_all_toctrees(builder, docnameset, docname, tree, colorfunc, traversed
|
||||
return tree
|
||||
|
||||
|
||||
def make_refnode(builder, fromdocname, todocname, targetid, child, title=None):
|
||||
# type: (Builder, str, str, str, nodes.Node, str) -> nodes.reference
|
||||
def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: str,
|
||||
child: Node, title: str = None) -> nodes.reference:
|
||||
"""Shortcut to create a reference node."""
|
||||
node = nodes.reference('', '', internal=True)
|
||||
if fromdocname == todocname and targetid:
|
||||
@ -465,19 +449,16 @@ def make_refnode(builder, fromdocname, todocname, targetid, child, title=None):
|
||||
return node
|
||||
|
||||
|
||||
def set_source_info(directive, node):
|
||||
# type: (Any, nodes.Node) -> None
|
||||
def set_source_info(directive: Directive, node: Node) -> None:
|
||||
node.source, node.line = \
|
||||
directive.state_machine.get_source_and_line(directive.lineno)
|
||||
|
||||
|
||||
def set_role_source_info(inliner, lineno, node):
|
||||
# type: (Inliner, int, nodes.Node) -> None
|
||||
def set_role_source_info(inliner: Inliner, lineno: int, node: Node) -> None:
|
||||
node.source, node.line = inliner.reporter.get_source_and_line(lineno) # type: ignore
|
||||
|
||||
|
||||
def copy_source_info(src, dst):
|
||||
# type: (nodes.Element, nodes.Element) -> None
|
||||
def copy_source_info(src: Element, dst: Element) -> None:
|
||||
dst.source = get_node_source(src)
|
||||
dst.line = get_node_line(src)
|
||||
|
||||
@ -493,8 +474,7 @@ NON_SMARTQUOTABLE_PARENT_NODES = (
|
||||
)
|
||||
|
||||
|
||||
def is_smartquotable(node):
|
||||
# type: (nodes.Node) -> bool
|
||||
def is_smartquotable(node: Node) -> bool:
|
||||
"""Check the node is smart-quotable or not."""
|
||||
if isinstance(node.parent, NON_SMARTQUOTABLE_PARENT_NODES):
|
||||
return False
|
||||
@ -506,8 +486,7 @@ def is_smartquotable(node):
|
||||
return True
|
||||
|
||||
|
||||
def process_only_nodes(document, tags):
|
||||
# type: (nodes.Node, Tags) -> None
|
||||
def process_only_nodes(document: Node, tags: "Tags") -> None:
|
||||
"""Filter ``only`` nodes which does not match *tags*."""
|
||||
for node in document.traverse(addnodes.only):
|
||||
try:
|
||||
@ -530,8 +509,7 @@ def process_only_nodes(document, tags):
|
||||
# monkey-patch Element.copy to copy the rawsource and line
|
||||
# for docutils-0.14 or older versions.
|
||||
|
||||
def _new_copy(self):
|
||||
# type: (nodes.Element) -> nodes.Element
|
||||
def _new_copy(self: Element) -> Element:
|
||||
newnode = self.__class__(self.rawsource, **self.attributes)
|
||||
if isinstance(self, nodes.Element):
|
||||
newnode.source = self.source
|
||||
|
@ -18,14 +18,11 @@ import sys
|
||||
import warnings
|
||||
from io import StringIO
|
||||
from os import path
|
||||
from typing import Any, Generator, Iterator, List, Tuple, Type
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
from sphinx.testing.path import path as Path
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Iterator, List, Tuple # NOQA
|
||||
|
||||
# Errnos that we need.
|
||||
EEXIST = getattr(errno, 'EEXIST', 0) # RemovedInSphinx40Warning
|
||||
ENOENT = getattr(errno, 'ENOENT', 0) # RemovedInSphinx40Warning
|
||||
@ -40,19 +37,16 @@ EINVAL = getattr(errno, 'EINVAL', 0) # RemovedInSphinx40Warning
|
||||
SEP = "/"
|
||||
|
||||
|
||||
def os_path(canonicalpath):
|
||||
# type: (str) -> str
|
||||
def os_path(canonicalpath: str) -> str:
|
||||
return canonicalpath.replace(SEP, path.sep)
|
||||
|
||||
|
||||
def canon_path(nativepath):
|
||||
# type: (str) -> str
|
||||
def canon_path(nativepath: str) -> str:
|
||||
"""Return path in OS-independent form"""
|
||||
return nativepath.replace(path.sep, SEP)
|
||||
|
||||
|
||||
def relative_uri(base, to):
|
||||
# type: (str, str) -> str
|
||||
def relative_uri(base: str, to: str) -> str:
|
||||
"""Return a relative URL from ``base`` to ``to``."""
|
||||
if to.startswith(SEP):
|
||||
return to
|
||||
@ -75,22 +69,19 @@ def relative_uri(base, to):
|
||||
return ('..' + SEP) * (len(b2) - 1) + SEP.join(t2)
|
||||
|
||||
|
||||
def ensuredir(path):
|
||||
# type: (str) -> None
|
||||
def ensuredir(path: str) -> None:
|
||||
"""Ensure that a path exists."""
|
||||
os.makedirs(path, exist_ok=True)
|
||||
|
||||
|
||||
def walk(top, topdown=True, followlinks=False):
|
||||
# type: (str, bool, bool) -> Iterator[Tuple[str, List[str], List[str]]]
|
||||
def walk(top: str, topdown: bool = True, followlinks: bool = False) -> Iterator[Tuple[str, List[str], List[str]]]: # NOQA
|
||||
warnings.warn('sphinx.util.osutil.walk() is deprecated for removal. '
|
||||
'Please use os.walk() instead.',
|
||||
RemovedInSphinx40Warning)
|
||||
return os.walk(top, topdown=topdown, followlinks=followlinks)
|
||||
|
||||
|
||||
def mtimes_of_files(dirnames, suffix):
|
||||
# type: (List[str], str) -> Iterator[float]
|
||||
def mtimes_of_files(dirnames: List[str], suffix: str) -> Iterator[float]:
|
||||
for dirname in dirnames:
|
||||
for root, dirs, files in os.walk(dirname):
|
||||
for sfile in files:
|
||||
@ -101,8 +92,7 @@ def mtimes_of_files(dirnames, suffix):
|
||||
pass
|
||||
|
||||
|
||||
def movefile(source, dest):
|
||||
# type: (str, str) -> None
|
||||
def movefile(source: str, dest: str) -> None:
|
||||
"""Move a file, removing the destination if it exists."""
|
||||
if os.path.exists(dest):
|
||||
try:
|
||||
@ -112,16 +102,14 @@ def movefile(source, dest):
|
||||
os.rename(source, dest)
|
||||
|
||||
|
||||
def copytimes(source, dest):
|
||||
# type: (str, str) -> None
|
||||
def copytimes(source: str, dest: str) -> None:
|
||||
"""Copy a file's modification times."""
|
||||
st = os.stat(source)
|
||||
if hasattr(os, 'utime'):
|
||||
os.utime(dest, (st.st_atime, st.st_mtime))
|
||||
|
||||
|
||||
def copyfile(source, dest):
|
||||
# type: (str, str) -> None
|
||||
def copyfile(source: str, dest: str) -> None:
|
||||
"""Copy a file and its modification times, if possible.
|
||||
|
||||
Note: ``copyfile`` skips copying if the file has not been changed"""
|
||||
@ -138,18 +126,15 @@ no_fn_re = re.compile(r'[^a-zA-Z0-9_-]')
|
||||
project_suffix_re = re.compile(' Documentation$')
|
||||
|
||||
|
||||
def make_filename(string):
|
||||
# type: (str) -> str
|
||||
def make_filename(string: str) -> str:
|
||||
return no_fn_re.sub('', string) or 'sphinx'
|
||||
|
||||
|
||||
def make_filename_from_project(project):
|
||||
# type: (str) -> str
|
||||
def make_filename_from_project(project: str) -> str:
|
||||
return make_filename(project_suffix_re.sub('', project)).lower()
|
||||
|
||||
|
||||
def relpath(path, start=os.curdir):
|
||||
# type: (str, str) -> str
|
||||
def relpath(path: str, start: str = os.curdir) -> str:
|
||||
"""Return a relative filepath to *path* either from the current directory or
|
||||
from an optional *start* directory.
|
||||
|
||||
@ -166,8 +151,7 @@ safe_relpath = relpath # for compatibility
|
||||
fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
|
||||
|
||||
def abspath(pathdir):
|
||||
# type: (str) -> str
|
||||
def abspath(pathdir: str) -> str:
|
||||
if isinstance(pathdir, Path):
|
||||
return pathdir.abspath()
|
||||
else:
|
||||
@ -182,8 +166,7 @@ def abspath(pathdir):
|
||||
return pathdir
|
||||
|
||||
|
||||
def getcwd():
|
||||
# type: () -> str
|
||||
def getcwd() -> str:
|
||||
warnings.warn('sphinx.util.osutil.getcwd() is deprecated. '
|
||||
'Please use os.getcwd() instead.',
|
||||
RemovedInSphinx40Warning)
|
||||
@ -191,8 +174,7 @@ def getcwd():
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def cd(target_dir):
|
||||
# type: (str) -> Iterator[None]
|
||||
def cd(target_dir: str) -> Generator[None, None, None]:
|
||||
cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(target_dir)
|
||||
@ -213,19 +195,16 @@ class FileAvoidWrite:
|
||||
|
||||
Objects can be used as context managers.
|
||||
"""
|
||||
def __init__(self, path):
|
||||
# type: (str) -> None
|
||||
def __init__(self, path: str) -> None:
|
||||
self._path = path
|
||||
self._io = None # type: StringIO
|
||||
|
||||
def write(self, data):
|
||||
# type: (str) -> None
|
||||
def write(self, data: str) -> None:
|
||||
if not self._io:
|
||||
self._io = StringIO()
|
||||
self._io.write(data)
|
||||
|
||||
def close(self):
|
||||
# type: () -> None
|
||||
def close(self) -> None:
|
||||
"""Stop accepting writes and write file, if needed."""
|
||||
if not self._io:
|
||||
raise Exception('FileAvoidWrite does not support empty files.')
|
||||
@ -244,16 +223,14 @@ class FileAvoidWrite:
|
||||
with open(self._path, 'w') as f:
|
||||
f.write(buf)
|
||||
|
||||
def __enter__(self):
|
||||
# type: () -> FileAvoidWrite
|
||||
def __enter__(self) -> "FileAvoidWrite":
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
# type: (str, str, str) -> None
|
||||
def __exit__(self, exc_type: Type[Exception], exc_value: Exception, traceback: Any) -> bool: # NOQA
|
||||
self.close()
|
||||
return True
|
||||
|
||||
def __getattr__(self, name):
|
||||
# type: (str) -> Any
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
# Proxy to _io instance.
|
||||
if not self._io:
|
||||
raise Exception('Must write to FileAvoidWrite before other '
|
||||
@ -262,8 +239,7 @@ class FileAvoidWrite:
|
||||
return getattr(self._io, name)
|
||||
|
||||
|
||||
def rmtree(path):
|
||||
# type: (str) -> None
|
||||
def rmtree(path: str) -> None:
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
|
@ -12,6 +12,7 @@ import os
|
||||
import time
|
||||
import traceback
|
||||
from math import sqrt
|
||||
from typing import Any, Callable, Dict, List, Sequence
|
||||
|
||||
try:
|
||||
import multiprocessing
|
||||
@ -21,10 +22,6 @@ except ImportError:
|
||||
from sphinx.errors import SphinxParallelError
|
||||
from sphinx.util import logging
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Dict, List, Sequence # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -35,12 +32,10 @@ parallel_available = multiprocessing and (os.name == 'posix')
|
||||
class SerialTasks:
|
||||
"""Has the same interface as ParallelTasks, but executes tasks directly."""
|
||||
|
||||
def __init__(self, nproc=1):
|
||||
# type: (int) -> None
|
||||
def __init__(self, nproc: int = 1) -> None:
|
||||
pass
|
||||
|
||||
def add_task(self, task_func, arg=None, result_func=None):
|
||||
# type: (Callable, Any, Callable) -> None
|
||||
def add_task(self, task_func: Callable, arg: Any = None, result_func: Callable = None) -> None: # NOQA
|
||||
if arg is not None:
|
||||
res = task_func(arg)
|
||||
else:
|
||||
@ -48,16 +43,14 @@ class SerialTasks:
|
||||
if result_func:
|
||||
result_func(res)
|
||||
|
||||
def join(self):
|
||||
# type: () -> None
|
||||
def join(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class ParallelTasks:
|
||||
"""Executes *nproc* tasks in parallel after forking."""
|
||||
|
||||
def __init__(self, nproc):
|
||||
# type: (int) -> None
|
||||
def __init__(self, nproc: int) -> None:
|
||||
self.nproc = nproc
|
||||
# (optional) function performed by each task on the result of main task
|
||||
self._result_funcs = {} # type: Dict[int, Callable]
|
||||
@ -74,8 +67,7 @@ class ParallelTasks:
|
||||
# task number of each subprocess
|
||||
self._taskid = 0
|
||||
|
||||
def _process(self, pipe, func, arg):
|
||||
# type: (Any, Callable, Any) -> None
|
||||
def _process(self, pipe: Any, func: Callable, arg: Any) -> None:
|
||||
try:
|
||||
collector = logging.LogCollector()
|
||||
with collector.collect():
|
||||
@ -91,8 +83,7 @@ class ParallelTasks:
|
||||
logging.convert_serializable(collector.logs)
|
||||
pipe.send((failed, collector.logs, ret))
|
||||
|
||||
def add_task(self, task_func, arg=None, result_func=None):
|
||||
# type: (Callable, Any, Callable) -> None
|
||||
def add_task(self, task_func: Callable, arg: Any = None, result_func: Callable = None) -> None: # NOQA
|
||||
tid = self._taskid
|
||||
self._taskid += 1
|
||||
self._result_funcs[tid] = result_func or (lambda arg, result: None)
|
||||
@ -104,13 +95,11 @@ class ParallelTasks:
|
||||
self._precvsWaiting[tid] = precv
|
||||
self._join_one()
|
||||
|
||||
def join(self):
|
||||
# type: () -> None
|
||||
def join(self) -> None:
|
||||
while self._pworking:
|
||||
self._join_one()
|
||||
|
||||
def _join_one(self):
|
||||
# type: () -> None
|
||||
def _join_one(self) -> None:
|
||||
for tid, pipe in self._precvs.items():
|
||||
if pipe.poll():
|
||||
exc, logs, result = pipe.recv()
|
||||
@ -132,8 +121,7 @@ class ParallelTasks:
|
||||
self._pworking += 1
|
||||
|
||||
|
||||
def make_chunks(arguments, nproc, maxbatch=10):
|
||||
# type: (Sequence[str], int, int) -> List[Any]
|
||||
def make_chunks(arguments: Sequence[str], nproc: int, maxbatch: int = 10) -> List[Any]:
|
||||
# determine how many documents to read in one go
|
||||
nargs = len(arguments)
|
||||
chunksize = nargs // nproc
|
||||
|
@ -20,8 +20,7 @@ DEPTH_CHUNK_START = b'tEXtDepth\x00'
|
||||
IEND_CHUNK = b'\x00\x00\x00\x00IEND\xAE\x42\x60\x82'
|
||||
|
||||
|
||||
def read_png_depth(filename):
|
||||
# type: (str) -> int
|
||||
def read_png_depth(filename: str) -> int:
|
||||
"""Read the special tEXt chunk indicating the depth from a PNG file."""
|
||||
with open(filename, 'rb') as f:
|
||||
f.seek(- (LEN_IEND + LEN_DEPTH), 2)
|
||||
@ -33,8 +32,7 @@ def read_png_depth(filename):
|
||||
return struct.unpack('!i', depthchunk[14:18])[0]
|
||||
|
||||
|
||||
def write_png_depth(filename, depth):
|
||||
# type: (str, int) -> None
|
||||
def write_png_depth(filename: str, depth: int) -> None:
|
||||
"""Write the special tEXt chunk indicating the depth to a PNG file.
|
||||
|
||||
The chunk is placed immediately before the special IEND chunk.
|
||||
|
@ -13,6 +13,7 @@ import io
|
||||
import sys
|
||||
import textwrap
|
||||
import warnings
|
||||
from typing import Any, Callable
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
|
||||
from sphinx.locale import __
|
||||
@ -20,10 +21,6 @@ from sphinx.util import logging
|
||||
from sphinx.util.console import terminal_safe
|
||||
from sphinx.util.typing import NoneType
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable # NOQA
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -33,8 +30,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# convert_with_2to3():
|
||||
# support for running 2to3 over config files
|
||||
def convert_with_2to3(filepath):
|
||||
# type: (str) -> str
|
||||
def convert_with_2to3(filepath: str) -> str:
|
||||
from lib2to3.refactor import RefactoringTool, get_fixers_from_package
|
||||
from lib2to3.pgen2.parse import ParseError
|
||||
fixers = get_fixers_from_package('lib2to3.fixes')
|
||||
@ -62,8 +58,7 @@ class UnicodeMixin:
|
||||
return self.__unicode__()
|
||||
|
||||
|
||||
def execfile_(filepath, _globals, open=open):
|
||||
# type: (str, Any, Callable) -> None
|
||||
def execfile_(filepath: str, _globals: Any, open: Callable = open) -> None:
|
||||
from sphinx.util.osutil import fs_encoding
|
||||
with open(filepath, 'rb') as f:
|
||||
source = f.read()
|
||||
|
@ -10,11 +10,14 @@
|
||||
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
from typing import Generator, Union
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import pkg_resources
|
||||
import requests
|
||||
|
||||
from sphinx.config import Config
|
||||
|
||||
try:
|
||||
from requests.packages.urllib3.exceptions import SSLError
|
||||
except ImportError:
|
||||
@ -54,17 +57,12 @@ else:
|
||||
pkg_resources.VersionConflict):
|
||||
pass # ignored
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Generator, Union # NOQA
|
||||
from sphinx.config import Config # NOQA
|
||||
|
||||
useragent_header = [('User-Agent',
|
||||
'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0')]
|
||||
|
||||
|
||||
def is_ssl_error(exc):
|
||||
# type: (Exception) -> bool
|
||||
def is_ssl_error(exc: Exception) -> bool:
|
||||
"""Check an exception is SSLError."""
|
||||
if isinstance(exc, SSLError):
|
||||
return True
|
||||
@ -77,8 +75,7 @@ def is_ssl_error(exc):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ignore_insecure_warning(**kwargs):
|
||||
# type: (Any) -> Generator
|
||||
def ignore_insecure_warning(**kwargs) -> Generator[None, None, None]:
|
||||
with warnings.catch_warnings():
|
||||
if not kwargs.get('verify') and InsecureRequestWarning:
|
||||
# ignore InsecureRequestWarning if verify=False
|
||||
@ -86,8 +83,7 @@ def ignore_insecure_warning(**kwargs):
|
||||
yield
|
||||
|
||||
|
||||
def _get_tls_cacert(url, config):
|
||||
# type: (str, Config) -> Union[str, bool]
|
||||
def _get_tls_cacert(url: str, config: Config) -> Union[str, bool]:
|
||||
"""Get additional CA cert for a specific URL.
|
||||
|
||||
This also returns ``False`` if verification is disabled.
|
||||
@ -109,8 +105,7 @@ def _get_tls_cacert(url, config):
|
||||
return certs.get(hostname, True)
|
||||
|
||||
|
||||
def get(url, **kwargs):
|
||||
# type: (str, Any) -> requests.Response
|
||||
def get(url: str, **kwargs) -> requests.Response:
|
||||
"""Sends a GET request like requests.get().
|
||||
|
||||
This sets up User-Agent header and TLS verification automatically."""
|
||||
@ -123,8 +118,7 @@ def get(url, **kwargs):
|
||||
return requests.get(url, **kwargs)
|
||||
|
||||
|
||||
def head(url, **kwargs):
|
||||
# type: (str, Any) -> requests.Response
|
||||
def head(url: str, **kwargs) -> requests.Response:
|
||||
"""Sends a HEAD request like requests.head().
|
||||
|
||||
This sets up User-Agent header and TLS verification automatically."""
|
||||
|
@ -11,23 +11,20 @@
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
from typing import Dict, Generator
|
||||
from unicodedata import east_asian_width
|
||||
|
||||
from docutils.parsers.rst import roles
|
||||
from docutils.parsers.rst.languages import en as english
|
||||
from docutils.statemachine import StringList
|
||||
from docutils.utils import Reporter
|
||||
from jinja2 import Environment
|
||||
from jinja2 import environmentfilter
|
||||
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import docutils
|
||||
from sphinx.util import logging
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Callable, Dict, Generator # NOQA
|
||||
from docutils.statemachine import StringList # NOQA
|
||||
from jinja2 import Environment # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
docinfo_re = re.compile(':\\w+:.*?')
|
||||
@ -40,18 +37,15 @@ WIDECHARS = defaultdict(lambda: "WF") # type: Dict[str, str]
|
||||
WIDECHARS["ja"] = "WFA" # In Japanese, Ambiguous characters also have double width
|
||||
|
||||
|
||||
def escape(text):
|
||||
# type: (str) -> str
|
||||
def escape(text: str) -> str:
|
||||
text = symbols_re.sub(r'\\\1', text)
|
||||
text = re.sub(r'^\.', r'\.', text) # escape a dot at top
|
||||
return text
|
||||
|
||||
|
||||
def textwidth(text, widechars='WF'):
|
||||
# type: (str, str) -> int
|
||||
def textwidth(text: str, widechars: str = 'WF') -> int:
|
||||
"""Get width of text."""
|
||||
def charwidth(char, widechars):
|
||||
# type: (str, str) -> int
|
||||
def charwidth(char: str, widechars: str) -> int:
|
||||
if east_asian_width(char) in widechars:
|
||||
return 2
|
||||
else:
|
||||
@ -61,8 +55,7 @@ def textwidth(text, widechars='WF'):
|
||||
|
||||
|
||||
@environmentfilter
|
||||
def heading(env, text, level=1):
|
||||
# type: (Environment, str, int) -> str
|
||||
def heading(env: Environment, text: str, level: int = 1) -> str:
|
||||
"""Create a heading for *level*."""
|
||||
assert level <= 3
|
||||
width = textwidth(text, WIDECHARS[env.language]) # type: ignore
|
||||
@ -71,8 +64,7 @@ def heading(env, text, level=1):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def default_role(docname, name):
|
||||
# type: (str, str) -> Generator
|
||||
def default_role(docname: str, name: str) -> Generator[None, None, None]:
|
||||
if name:
|
||||
dummy_reporter = Reporter('', 4, 4)
|
||||
role_fn, _ = roles.role(name, english, 0, dummy_reporter)
|
||||
@ -86,8 +78,7 @@ def default_role(docname, name):
|
||||
docutils.unregister_role('')
|
||||
|
||||
|
||||
def prepend_prolog(content, prolog):
|
||||
# type: (StringList, str) -> None
|
||||
def prepend_prolog(content: StringList, prolog: str) -> None:
|
||||
"""Prepend a string to content body as prolog."""
|
||||
if prolog:
|
||||
pos = 0
|
||||
@ -109,8 +100,7 @@ def prepend_prolog(content, prolog):
|
||||
content.insert(pos + lineno + 1, '', '<generated>', 0)
|
||||
|
||||
|
||||
def append_epilog(content, epilog):
|
||||
# type: (StringList, str) -> None
|
||||
def append_epilog(content: StringList, epilog: str) -> None:
|
||||
"""Append a string to content body as epilog."""
|
||||
if epilog:
|
||||
content.append('', '<generated>', 0)
|
||||
|
@ -26,14 +26,12 @@
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Generator, Iterable, Tuple
|
||||
|
||||
from docutils.utils import smartquotes
|
||||
|
||||
from sphinx.util.docutils import __version_info__ as docutils_version
|
||||
|
||||
if False: # For type annotation
|
||||
from typing import Generator, Iterable, Tuple # NOQA
|
||||
|
||||
|
||||
langquotes = {'af': '“”‘’',
|
||||
'af-x-altquot': '„”‚’',
|
||||
@ -125,8 +123,7 @@ langquotes = {'af': '“”‘’',
|
||||
}
|
||||
|
||||
|
||||
def educateQuotes(text, language='en'):
|
||||
# type: (str, str) -> str
|
||||
def educateQuotes(text: str, language: str = 'en') -> str:
|
||||
"""
|
||||
Parameter: - text string (unicode or bytes).
|
||||
- language (`BCP 47` language tag.)
|
||||
@ -240,8 +237,10 @@ def educateQuotes(text, language='en'):
|
||||
return text
|
||||
|
||||
|
||||
def educate_tokens(text_tokens, attr=smartquotes.default_smartypants_attr, language='en'):
|
||||
# type: (Iterable[Tuple[str, str]], str, str) -> Generator[str, None, None]
|
||||
def educate_tokens(text_tokens: Iterable[Tuple[str, str]],
|
||||
attr: str = smartquotes.default_smartypants_attr,
|
||||
language: str = 'en'
|
||||
) -> Generator[str, None, None]:
|
||||
"""Return iterator that "educates" the items of `text_tokens`.
|
||||
|
||||
This is modified to intercept the ``attr='2'`` as it was used by the
|
||||
|
@ -18,18 +18,15 @@ except ImportError:
|
||||
|
||||
|
||||
class BaseStemmer:
|
||||
def stem(self, word):
|
||||
# type: (str) -> str
|
||||
def stem(self, word: str) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class PyStemmer(BaseStemmer):
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
def __init__(self) -> None:
|
||||
self.stemmer = _PyStemmer('porter')
|
||||
|
||||
def stem(self, word):
|
||||
# type: (str) -> str
|
||||
def stem(self, word: str) -> str:
|
||||
return self.stemmer.stemWord(word)
|
||||
|
||||
|
||||
@ -37,13 +34,11 @@ class StandardStemmer(PorterStemmer, BaseStemmer): # type: ignore
|
||||
"""All those porter stemmer implementations look hideous;
|
||||
make at least the stem method nicer.
|
||||
"""
|
||||
def stem(self, word): # type: ignore
|
||||
# type: (str) -> str
|
||||
def stem(self, word: str) -> str: # type: ignore
|
||||
return super().stem(word, 0, len(word) - 1)
|
||||
|
||||
|
||||
def get_stemmer():
|
||||
# type: () -> BaseStemmer
|
||||
def get_stemmer() -> BaseStemmer:
|
||||
if PYSTEMMER:
|
||||
return PyStemmer()
|
||||
else:
|
||||
|
@ -30,8 +30,7 @@
|
||||
|
||||
class PorterStemmer:
|
||||
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
def __init__(self) -> None:
|
||||
"""The main part of the stemming algorithm starts here.
|
||||
b is a buffer holding a word to be stemmed. The letters are in b[k0],
|
||||
b[k0+1] ... ending at b[k]. In fact k0 = 0 in this demo program. k is
|
||||
@ -47,8 +46,7 @@ class PorterStemmer:
|
||||
self.k0 = 0
|
||||
self.j = 0 # j is a general offset into the string
|
||||
|
||||
def cons(self, i):
|
||||
# type: (int) -> int
|
||||
def cons(self, i: int) -> int:
|
||||
"""cons(i) is TRUE <=> b[i] is a consonant."""
|
||||
if self.b[i] == 'a' or self.b[i] == 'e' or self.b[i] == 'i' \
|
||||
or self.b[i] == 'o' or self.b[i] == 'u':
|
||||
@ -60,8 +58,7 @@ class PorterStemmer:
|
||||
return (not self.cons(i - 1))
|
||||
return 1
|
||||
|
||||
def m(self):
|
||||
# type: () -> int
|
||||
def m(self) -> int:
|
||||
"""m() measures the number of consonant sequences between k0 and j.
|
||||
if c is a consonant sequence and v a vowel sequence, and <..>
|
||||
indicates arbitrary presence,
|
||||
@ -98,16 +95,14 @@ class PorterStemmer:
|
||||
i = i + 1
|
||||
i = i + 1
|
||||
|
||||
def vowelinstem(self):
|
||||
# type: () -> int
|
||||
def vowelinstem(self) -> int:
|
||||
"""vowelinstem() is TRUE <=> k0,...j contains a vowel"""
|
||||
for i in range(self.k0, self.j + 1):
|
||||
if not self.cons(i):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def doublec(self, j):
|
||||
# type: (int) -> int
|
||||
def doublec(self, j: int) -> int:
|
||||
"""doublec(j) is TRUE <=> j,(j-1) contain a double consonant."""
|
||||
if j < (self.k0 + 1):
|
||||
return 0
|
||||
@ -115,8 +110,7 @@ class PorterStemmer:
|
||||
return 0
|
||||
return self.cons(j)
|
||||
|
||||
def cvc(self, i):
|
||||
# type: (int) -> int
|
||||
def cvc(self, i: int) -> int:
|
||||
"""cvc(i) is TRUE <=> i-2,i-1,i has the form
|
||||
consonant - vowel - consonant
|
||||
and also if the second c is not w,x or y. this is used when trying to
|
||||
@ -133,8 +127,7 @@ class PorterStemmer:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def ends(self, s):
|
||||
# type: (str) -> int
|
||||
def ends(self, s: str) -> int:
|
||||
"""ends(s) is TRUE <=> k0,...k ends with the string s."""
|
||||
length = len(s)
|
||||
if s[length - 1] != self.b[self.k]: # tiny speed-up
|
||||
@ -146,22 +139,19 @@ class PorterStemmer:
|
||||
self.j = self.k - length
|
||||
return 1
|
||||
|
||||
def setto(self, s):
|
||||
# type: (str) -> None
|
||||
def setto(self, s: str) -> None:
|
||||
"""setto(s) sets (j+1),...k to the characters in the string s,
|
||||
readjusting k."""
|
||||
length = len(s)
|
||||
self.b = self.b[:self.j + 1] + s + self.b[self.j + length + 1:]
|
||||
self.k = self.j + length
|
||||
|
||||
def r(self, s):
|
||||
# type: (str) -> None
|
||||
def r(self, s: str) -> None:
|
||||
"""r(s) is used further down."""
|
||||
if self.m() > 0:
|
||||
self.setto(s)
|
||||
|
||||
def step1ab(self):
|
||||
# type: () -> None
|
||||
def step1ab(self) -> None:
|
||||
"""step1ab() gets rid of plurals and -ed or -ing. e.g.
|
||||
|
||||
caresses -> caress
|
||||
@ -208,15 +198,13 @@ class PorterStemmer:
|
||||
elif (self.m() == 1 and self.cvc(self.k)):
|
||||
self.setto("e")
|
||||
|
||||
def step1c(self):
|
||||
# type: () -> None
|
||||
def step1c(self) -> None:
|
||||
"""step1c() turns terminal y to i when there is another vowel in
|
||||
the stem."""
|
||||
if (self.ends("y") and self.vowelinstem()):
|
||||
self.b = self.b[:self.k] + 'i' + self.b[self.k + 1:]
|
||||
|
||||
def step2(self):
|
||||
# type: () -> None
|
||||
def step2(self) -> None:
|
||||
"""step2() maps double suffices to single ones.
|
||||
so -ization ( = -ize plus -ation) maps to -ize etc. note that the
|
||||
string before the suffix must give m() > 0.
|
||||
@ -275,8 +263,7 @@ class PorterStemmer:
|
||||
self.r("log")
|
||||
# To match the published algorithm, delete this phrase
|
||||
|
||||
def step3(self):
|
||||
# type: () -> None
|
||||
def step3(self) -> None:
|
||||
"""step3() dels with -ic-, -full, -ness etc. similar strategy
|
||||
to step2."""
|
||||
if self.b[self.k] == 'e':
|
||||
@ -298,8 +285,7 @@ class PorterStemmer:
|
||||
if self.ends("ness"):
|
||||
self.r("")
|
||||
|
||||
def step4(self):
|
||||
# type: () -> None
|
||||
def step4(self) -> None:
|
||||
"""step4() takes off -ant, -ence etc., in context <c>vcvc<v>."""
|
||||
if self.b[self.k - 1] == 'a':
|
||||
if self.ends("al"):
|
||||
@ -382,8 +368,7 @@ class PorterStemmer:
|
||||
if self.m() > 1:
|
||||
self.k = self.j
|
||||
|
||||
def step5(self):
|
||||
# type: () -> None
|
||||
def step5(self) -> None:
|
||||
"""step5() removes a final -e if m() > 1, and changes -ll to -l if
|
||||
m() > 1.
|
||||
"""
|
||||
@ -395,8 +380,7 @@ class PorterStemmer:
|
||||
if self.b[self.k] == 'l' and self.doublec(self.k) and self.m() > 1:
|
||||
self.k = self.k - 1
|
||||
|
||||
def stem(self, p, i, j):
|
||||
# type: (str, int, int) -> str
|
||||
def stem(self, p: str, i: int, j: int) -> str:
|
||||
"""In stem(p,i,j), p is a char pointer, and the string to be stemmed
|
||||
is from p[i] to p[j] inclusive. Typically i is zero and j is the
|
||||
offset to the last character of a string, (p[j+1] == '\0'). The
|
||||
|
@ -6,26 +6,23 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
# (ab)use the Jinja parser for parsing our boolean expressions
|
||||
from typing import Iterator, List
|
||||
|
||||
from jinja2 import nodes
|
||||
from jinja2.environment import Environment
|
||||
from jinja2.nodes import Node
|
||||
from jinja2.parser import Parser
|
||||
|
||||
env = Environment()
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Iterator, List # NOQA
|
||||
|
||||
|
||||
class BooleanParser(Parser):
|
||||
"""
|
||||
Only allow condition exprs and/or/not operations.
|
||||
"""
|
||||
|
||||
def parse_compare(self):
|
||||
# type: () -> nodes.Node
|
||||
node = None # type: nodes.Node
|
||||
def parse_compare(self) -> Node:
|
||||
node = None # type: Node
|
||||
token = self.stream.current
|
||||
if token.type == 'name':
|
||||
if token.value in ('true', 'false', 'True', 'False'):
|
||||
@ -46,38 +43,31 @@ class BooleanParser(Parser):
|
||||
|
||||
|
||||
class Tags:
|
||||
def __init__(self, tags=None):
|
||||
# type: (List[str]) -> None
|
||||
def __init__(self, tags: List[str] = None) -> None:
|
||||
self.tags = dict.fromkeys(tags or [], True)
|
||||
|
||||
def has(self, tag):
|
||||
# type: (str) -> bool
|
||||
def has(self, tag: str) -> bool:
|
||||
return tag in self.tags
|
||||
|
||||
__contains__ = has
|
||||
|
||||
def __iter__(self):
|
||||
# type: () -> Iterator[str]
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
return iter(self.tags)
|
||||
|
||||
def add(self, tag):
|
||||
# type: (str) -> None
|
||||
def add(self, tag: str) -> None:
|
||||
self.tags[tag] = True
|
||||
|
||||
def remove(self, tag):
|
||||
# type: (str) -> None
|
||||
def remove(self, tag: str) -> None:
|
||||
self.tags.pop(tag, None)
|
||||
|
||||
def eval_condition(self, condition):
|
||||
# type: (str) -> bool
|
||||
def eval_condition(self, condition: str) -> bool:
|
||||
# exceptions are handled by the caller
|
||||
parser = BooleanParser(env, condition, state='variable')
|
||||
expr = parser.parse_expression()
|
||||
if not parser.stream.eos:
|
||||
raise ValueError('chunk after expression')
|
||||
|
||||
def eval_node(node):
|
||||
# type: (nodes.Node) -> bool
|
||||
def eval_node(node: Node) -> bool:
|
||||
if isinstance(node, nodes.CondExpr):
|
||||
if eval_node(node.test): # type: ignore
|
||||
return eval_node(node.expr1) # type: ignore
|
||||
|
@ -9,7 +9,9 @@
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Dict
|
||||
|
||||
from jinja2.loaders import BaseLoader
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
|
||||
from sphinx import package_dir
|
||||
@ -17,58 +19,45 @@ from sphinx.jinja2glue import SphinxFileSystemLoader
|
||||
from sphinx.locale import get_translator
|
||||
from sphinx.util import rst, texescape
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Dict # NOQA
|
||||
from jinja2.loaders import BaseLoader # NOQA
|
||||
|
||||
|
||||
class BaseRenderer:
|
||||
def __init__(self, loader=None):
|
||||
# type: (BaseLoader) -> None
|
||||
def __init__(self, loader: BaseLoader = None) -> None:
|
||||
self.env = SandboxedEnvironment(loader=loader, extensions=['jinja2.ext.i18n'])
|
||||
self.env.filters['repr'] = repr
|
||||
self.env.install_gettext_translations(get_translator()) # type: ignore
|
||||
|
||||
def render(self, template_name, context):
|
||||
# type: (str, Dict) -> str
|
||||
def render(self, template_name: str, context: Dict) -> str:
|
||||
return self.env.get_template(template_name).render(context)
|
||||
|
||||
def render_string(self, source, context):
|
||||
# type: (str, Dict) -> str
|
||||
def render_string(self, source: str, context: Dict) -> str:
|
||||
return self.env.from_string(source).render(context)
|
||||
|
||||
|
||||
class FileRenderer(BaseRenderer):
|
||||
def __init__(self, search_path):
|
||||
# type: (str) -> None
|
||||
def __init__(self, search_path: str) -> None:
|
||||
loader = SphinxFileSystemLoader(search_path)
|
||||
super().__init__(loader)
|
||||
|
||||
@classmethod
|
||||
def render_from_file(cls, filename, context):
|
||||
# type: (str, Dict) -> str
|
||||
def render_from_file(cls, filename: str, context: Dict) -> str:
|
||||
dirname = os.path.dirname(filename)
|
||||
basename = os.path.basename(filename)
|
||||
return cls(dirname).render(basename, context)
|
||||
|
||||
|
||||
class SphinxRenderer(FileRenderer):
|
||||
def __init__(self, template_path=None):
|
||||
# type: (str) -> None
|
||||
def __init__(self, template_path: str = None) -> None:
|
||||
if template_path is None:
|
||||
template_path = os.path.join(package_dir, 'templates')
|
||||
super().__init__(template_path)
|
||||
|
||||
@classmethod
|
||||
def render_from_file(cls, filename, context):
|
||||
# type: (str, Dict) -> str
|
||||
def render_from_file(cls, filename: str, context: Dict) -> str:
|
||||
return FileRenderer.render_from_file(filename, context)
|
||||
|
||||
|
||||
class LaTeXRenderer(SphinxRenderer):
|
||||
def __init__(self, template_path=None):
|
||||
# type: (str) -> None
|
||||
def __init__(self, template_path: str = None) -> None:
|
||||
if template_path is None:
|
||||
template_path = os.path.join(package_dir, 'templates', 'latex')
|
||||
super().__init__(template_path)
|
||||
@ -87,8 +76,7 @@ class LaTeXRenderer(SphinxRenderer):
|
||||
|
||||
|
||||
class ReSTRenderer(SphinxRenderer):
|
||||
def __init__(self, template_path=None, language=None):
|
||||
# type: (str, str) -> None
|
||||
def __init__(self, template_path: str = None, language: str = None) -> None:
|
||||
super().__init__(template_path)
|
||||
|
||||
# add language to environment
|
||||
|
@ -9,10 +9,7 @@
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Dict # NOQA
|
||||
from typing import Dict
|
||||
|
||||
tex_replacements = [
|
||||
# map TeX special chars
|
||||
@ -78,20 +75,17 @@ tex_replace_map = {}
|
||||
tex_hl_escape_map_new = {}
|
||||
|
||||
|
||||
def escape(s):
|
||||
# type: (str) -> str
|
||||
def escape(s: str) -> str:
|
||||
"""Escape text for LaTeX output."""
|
||||
return s.translate(tex_escape_map)
|
||||
|
||||
|
||||
def escape_abbr(text):
|
||||
# type: (str) -> str
|
||||
def escape_abbr(text: str) -> str:
|
||||
"""Adjust spacing after abbreviations. Works with @ letter or other."""
|
||||
return re.sub(r'\.(?=\s|$)', r'.\@{}', text)
|
||||
|
||||
|
||||
def init():
|
||||
# type: () -> None
|
||||
def init() -> None:
|
||||
for a, b in tex_replacements:
|
||||
tex_escape_map[ord(a)] = b
|
||||
tex_replace_map[ord(a)] = '_'
|
||||
|
@ -23,6 +23,9 @@ TextlikeNode = Union[nodes.Text, nodes.TextElement]
|
||||
# type of None
|
||||
NoneType = type(None)
|
||||
|
||||
# path matcher
|
||||
PathMatcher = Callable[[str], bool]
|
||||
|
||||
# common role functions
|
||||
RoleFunction = Callable[[str, str, str, int, Inliner, Dict, List[str]],
|
||||
Tuple[List[nodes.Node], List[nodes.system_message]]]
|
||||
|
@ -22,31 +22,6 @@ class CustomEx(Exception):
|
||||
"""Exception method."""
|
||||
|
||||
|
||||
class CustomDataDescriptor(object):
|
||||
"""Descriptor class docstring."""
|
||||
|
||||
def __init__(self, doc):
|
||||
self.__doc__ = doc
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return self
|
||||
return 42
|
||||
|
||||
def meth(self):
|
||||
"""Function."""
|
||||
return "The Answer"
|
||||
|
||||
|
||||
class CustomDataDescriptorMeta(type):
|
||||
"""Descriptor metaclass docstring."""
|
||||
|
||||
|
||||
class CustomDataDescriptor2(CustomDataDescriptor):
|
||||
"""Descriptor class with custom metaclass docstring."""
|
||||
__metaclass__ = CustomDataDescriptorMeta
|
||||
|
||||
|
||||
def _funky_classmethod(name, b, c, d, docstring=None):
|
||||
"""Generates a classmethod for a class from a template by filling out
|
||||
some arguments."""
|
||||
@ -59,30 +34,9 @@ def _funky_classmethod(name, b, c, d, docstring=None):
|
||||
return classmethod(function)
|
||||
|
||||
|
||||
class Base(object):
|
||||
def inheritedmeth(self):
|
||||
"""Inherited function."""
|
||||
|
||||
@classmethod
|
||||
def inheritedclassmeth(cls):
|
||||
"""Inherited class method."""
|
||||
|
||||
@staticmethod
|
||||
def inheritedstaticmeth(cls):
|
||||
"""Inherited static method."""
|
||||
|
||||
|
||||
class Derived(Base):
|
||||
def inheritedmeth(self):
|
||||
# no docstring here
|
||||
pass
|
||||
|
||||
|
||||
class Class(Base):
|
||||
class Class(object):
|
||||
"""Class to document."""
|
||||
|
||||
descr = CustomDataDescriptor("Descriptor instance docstring.")
|
||||
|
||||
def meth(self):
|
||||
"""Function."""
|
||||
|
||||
@ -101,10 +55,6 @@ class Class(Base):
|
||||
#: should be documented -- süß
|
||||
attr = 'bar'
|
||||
|
||||
@property
|
||||
def prop(self):
|
||||
"""Property."""
|
||||
|
||||
docattr = 'baz'
|
||||
"""should likewise be documented -- süß"""
|
||||
|
||||
|
35
tests/roots/test-ext-autodoc/target/autoclass_content.py
Normal file
35
tests/roots/test-ext-autodoc/target/autoclass_content.py
Normal file
@ -0,0 +1,35 @@
|
||||
class A:
|
||||
"""A class having no __init__, no __new__"""
|
||||
|
||||
|
||||
class B:
|
||||
"""A class having __init__(no docstring), no __new__"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
class C:
|
||||
"""A class having __init__, no __new__"""
|
||||
def __init__(self):
|
||||
"""__init__ docstring"""
|
||||
|
||||
|
||||
class D:
|
||||
"""A class having no __init__, __new__(no docstring)"""
|
||||
def __new__(cls):
|
||||
pass
|
||||
|
||||
|
||||
class E:
|
||||
"""A class having no __init__, __new__"""
|
||||
def __new__(cls):
|
||||
"""__new__ docstring"""
|
||||
|
||||
|
||||
class F:
|
||||
"""A class having both __init__ and __new__"""
|
||||
def __init__(self):
|
||||
"""__init__ docstring"""
|
||||
|
||||
def __new__(cls):
|
||||
"""__new__ docstring"""
|
31
tests/roots/test-ext-autodoc/target/descriptor.py
Normal file
31
tests/roots/test-ext-autodoc/target/descriptor.py
Normal file
@ -0,0 +1,31 @@
|
||||
class CustomDataDescriptor(object):
|
||||
"""Descriptor class docstring."""
|
||||
|
||||
def __init__(self, doc):
|
||||
self.__doc__ = doc
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return self
|
||||
return 42
|
||||
|
||||
def meth(self):
|
||||
"""Function."""
|
||||
return "The Answer"
|
||||
|
||||
|
||||
class CustomDataDescriptorMeta(type):
|
||||
"""Descriptor metaclass docstring."""
|
||||
|
||||
|
||||
class CustomDataDescriptor2(CustomDataDescriptor):
|
||||
"""Descriptor class with custom metaclass docstring."""
|
||||
__metaclass__ = CustomDataDescriptorMeta
|
||||
|
||||
|
||||
class Class:
|
||||
descr = CustomDataDescriptor("Descriptor instance docstring.")
|
||||
|
||||
@property
|
||||
def prop(self):
|
||||
"""Property."""
|
19
tests/roots/test-ext-autodoc/target/docstring_signature.py
Normal file
19
tests/roots/test-ext-autodoc/target/docstring_signature.py
Normal file
@ -0,0 +1,19 @@
|
||||
class A:
|
||||
"""A(foo, bar)"""
|
||||
|
||||
|
||||
class B:
|
||||
"""B(foo, bar)"""
|
||||
def __init__(self):
|
||||
"""B(foo, bar, baz)"""
|
||||
|
||||
|
||||
class C:
|
||||
"""C(foo, bar)"""
|
||||
def __new__(cls):
|
||||
"""C(foo, bar, baz)"""
|
||||
|
||||
|
||||
class D:
|
||||
def __init__(self):
|
||||
"""D(foo, bar, baz)"""
|
19
tests/roots/test-ext-autodoc/target/inheritance.py
Normal file
19
tests/roots/test-ext-autodoc/target/inheritance.py
Normal file
@ -0,0 +1,19 @@
|
||||
class Base(object):
|
||||
def inheritedmeth(self):
|
||||
"""Inherited function."""
|
||||
|
||||
@classmethod
|
||||
def inheritedclassmeth(cls):
|
||||
"""Inherited class method."""
|
||||
|
||||
@staticmethod
|
||||
def inheritedstaticmeth(cls):
|
||||
"""Inherited static method."""
|
||||
|
||||
|
||||
class Derived(Base):
|
||||
def inheritedmeth(self):
|
||||
# no docstring here
|
||||
pass
|
||||
|
||||
|
8
tests/roots/test-ext-autodoc/target/process_docstring.py
Normal file
8
tests/roots/test-ext-autodoc/target/process_docstring.py
Normal file
@ -0,0 +1,8 @@
|
||||
def func():
|
||||
"""
|
||||
first line
|
||||
---
|
||||
second line
|
||||
---
|
||||
third line
|
||||
"""
|
1
tests/roots/test-roles-download/another/dummy.dat
Normal file
1
tests/roots/test-roles-download/another/dummy.dat
Normal file
@ -0,0 +1 @@
|
||||
this one will have some content
|
@ -2,5 +2,6 @@ test-roles-download
|
||||
===================
|
||||
|
||||
* :download:`dummy.dat`
|
||||
* :download:`another/dummy.dat`
|
||||
* :download:`not_found.dat`
|
||||
* :download:`Sphinx logo <http://www.sphinx-doc.org/en/master/_static/sphinxheader.png>`
|
||||
|
@ -9,7 +9,6 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import platform
|
||||
import sys
|
||||
from unittest.mock import Mock
|
||||
from warnings import catch_warnings
|
||||
@ -17,7 +16,7 @@ from warnings import catch_warnings
|
||||
import pytest
|
||||
from docutils.statemachine import ViewList
|
||||
|
||||
from sphinx.ext.autodoc import ModuleLevelDocumenter, cut_lines, between, ALL, Options
|
||||
from sphinx.ext.autodoc import ModuleLevelDocumenter, ALL, Options
|
||||
from sphinx.ext.autodoc.directive import DocumenterBridge, process_documenter_options
|
||||
from sphinx.testing.util import SphinxTestApp, Struct # NOQA
|
||||
from sphinx.util import logging
|
||||
@ -25,8 +24,6 @@ from sphinx.util.docutils import LoggingReporter
|
||||
|
||||
app = None
|
||||
|
||||
IS_PYPY = platform.python_implementation() == 'PyPy'
|
||||
|
||||
|
||||
def do_autodoc(app, objtype, name, options=None):
|
||||
if options is None:
|
||||
@ -55,7 +52,6 @@ def setup_module(rootdir, sphinx_test_tempdir):
|
||||
app = SphinxTestApp(srcdir=srcdir)
|
||||
app.builder.env.app = app
|
||||
app.builder.env.temp_data['docname'] = 'dummy'
|
||||
app.connect('autodoc-process-docstring', process_docstring)
|
||||
app.connect('autodoc-process-signature', process_signature)
|
||||
app.connect('autodoc-skip-member', skip_member)
|
||||
yield
|
||||
@ -70,7 +66,7 @@ directive = options = None
|
||||
@pytest.fixture
|
||||
def setup_test():
|
||||
global options, directive
|
||||
global processed_docstrings, processed_signatures
|
||||
global processed_signatures
|
||||
|
||||
options = Options(
|
||||
inherited_members = False,
|
||||
@ -99,7 +95,6 @@ def setup_test():
|
||||
)
|
||||
directive.state.document.settings.tab_width = 8
|
||||
|
||||
processed_docstrings = []
|
||||
processed_signatures = []
|
||||
|
||||
app._status.truncate(0)
|
||||
@ -110,16 +105,9 @@ def setup_test():
|
||||
app.registry.autodoc_attrgettrs.clear()
|
||||
|
||||
|
||||
processed_docstrings = []
|
||||
processed_signatures = []
|
||||
|
||||
|
||||
def process_docstring(app, what, name, obj, options, lines):
|
||||
processed_docstrings.append((what, name))
|
||||
if name == 'bar':
|
||||
lines.extend(['42', ''])
|
||||
|
||||
|
||||
def process_signature(app, what, name, obj, options, args, retann):
|
||||
processed_signatures.append((what, name))
|
||||
if name == 'bar':
|
||||
@ -337,127 +325,6 @@ def test_get_doc():
|
||||
"""Döcstring"""
|
||||
assert getdocl('function', f) == ['Döcstring']
|
||||
|
||||
# class docstring: depends on config value which one is taken
|
||||
class C:
|
||||
"""Class docstring"""
|
||||
def __init__(self):
|
||||
"""Init docstring"""
|
||||
|
||||
def __new__(cls):
|
||||
"""New docstring"""
|
||||
directive.env.config.autoclass_content = 'class'
|
||||
assert getdocl('class', C) == ['Class docstring']
|
||||
directive.env.config.autoclass_content = 'init'
|
||||
assert getdocl('class', C) == ['Init docstring']
|
||||
directive.env.config.autoclass_content = 'both'
|
||||
assert getdocl('class', C) == ['Class docstring', '', 'Init docstring']
|
||||
|
||||
class D:
|
||||
"""Class docstring"""
|
||||
def __init__(self):
|
||||
"""Init docstring
|
||||
|
||||
Other
|
||||
lines
|
||||
"""
|
||||
|
||||
# Indentation is normalized for 'both'
|
||||
assert getdocl('class', D) == ['Class docstring', '', 'Init docstring',
|
||||
'', 'Other', ' lines']
|
||||
|
||||
# __init__ have signature at first line of docstring
|
||||
class E:
|
||||
"""Class docstring"""
|
||||
def __init__(self, *args, **kw):
|
||||
"""
|
||||
__init__(a1, a2, kw1=True, kw2=False)
|
||||
|
||||
Init docstring
|
||||
"""
|
||||
|
||||
# signature line in the docstring will be kept when
|
||||
# autodoc_docstring_signature == False
|
||||
directive.env.config.autodoc_docstring_signature = False
|
||||
directive.env.config.autoclass_content = 'class'
|
||||
assert getdocl('class', E) == ['Class docstring']
|
||||
directive.env.config.autoclass_content = 'init'
|
||||
assert getdocl('class', E) == ['__init__(a1, a2, kw1=True, kw2=False)',
|
||||
'', 'Init docstring']
|
||||
directive.env.config.autoclass_content = 'both'
|
||||
assert getdocl('class', E) == ['Class docstring', '',
|
||||
'__init__(a1, a2, kw1=True, kw2=False)',
|
||||
'', 'Init docstring']
|
||||
|
||||
# signature line in the docstring will be removed when
|
||||
# autodoc_docstring_signature == True
|
||||
directive.env.config.autodoc_docstring_signature = True # default
|
||||
directive.env.config.autoclass_content = 'class'
|
||||
assert getdocl('class', E) == ['Class docstring']
|
||||
directive.env.config.autoclass_content = 'init'
|
||||
assert getdocl('class', E) == ['Init docstring']
|
||||
directive.env.config.autoclass_content = 'both'
|
||||
assert getdocl('class', E) == ['Class docstring', '', 'Init docstring']
|
||||
|
||||
# class does not have __init__ method
|
||||
class F:
|
||||
"""Class docstring"""
|
||||
|
||||
# docstring in the __init__ method of base class will be discard
|
||||
for f in (False, True):
|
||||
directive.env.config.autodoc_docstring_signature = f
|
||||
directive.env.config.autoclass_content = 'class'
|
||||
assert getdocl('class', F) == ['Class docstring']
|
||||
directive.env.config.autoclass_content = 'init'
|
||||
assert getdocl('class', F) == ['Class docstring']
|
||||
directive.env.config.autoclass_content = 'both'
|
||||
assert getdocl('class', F) == ['Class docstring']
|
||||
|
||||
# class has __init__ method with no docstring
|
||||
class G:
|
||||
"""Class docstring"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# docstring in the __init__ method of base class will not be used
|
||||
for f in (False, True):
|
||||
directive.env.config.autodoc_docstring_signature = f
|
||||
directive.env.config.autoclass_content = 'class'
|
||||
assert getdocl('class', G) == ['Class docstring']
|
||||
directive.env.config.autoclass_content = 'init'
|
||||
assert getdocl('class', G) == ['Class docstring']
|
||||
directive.env.config.autoclass_content = 'both'
|
||||
assert getdocl('class', G) == ['Class docstring']
|
||||
|
||||
# class has __new__ method with docstring
|
||||
# class docstring: depends on config value which one is taken
|
||||
class H:
|
||||
"""Class docstring"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __new__(cls):
|
||||
"""New docstring"""
|
||||
directive.env.config.autoclass_content = 'class'
|
||||
assert getdocl('class', H) == ['Class docstring']
|
||||
directive.env.config.autoclass_content = 'init'
|
||||
assert getdocl('class', H) == ['New docstring']
|
||||
directive.env.config.autoclass_content = 'both'
|
||||
assert getdocl('class', H) == ['Class docstring', '', 'New docstring']
|
||||
|
||||
# class has __init__ method without docstring and
|
||||
# __new__ method with docstring
|
||||
# class docstring: depends on config value which one is taken
|
||||
class I: # NOQA
|
||||
"""Class docstring"""
|
||||
def __new__(cls):
|
||||
"""New docstring"""
|
||||
directive.env.config.autoclass_content = 'class'
|
||||
assert getdocl('class', I) == ['Class docstring']
|
||||
directive.env.config.autoclass_content = 'init'
|
||||
assert getdocl('class', I) == ['New docstring']
|
||||
directive.env.config.autoclass_content = 'both'
|
||||
assert getdocl('class', I) == ['Class docstring', '', 'New docstring']
|
||||
|
||||
# verify that method docstrings get extracted in both normal case
|
||||
# and in case of bound method posing as a function
|
||||
class J: # NOQA
|
||||
@ -466,70 +333,6 @@ def test_get_doc():
|
||||
assert getdocl('method', J.foo) == ['Method docstring']
|
||||
assert getdocl('function', J().foo) == ['Method docstring']
|
||||
|
||||
from target import Base, Derived
|
||||
|
||||
# NOTE: inspect.getdoc seems not to work with locally defined classes
|
||||
directive.env.config.autodoc_inherit_docstrings = False
|
||||
assert getdocl('method', Base.inheritedmeth) == ['Inherited function.']
|
||||
assert getdocl('method', Derived.inheritedmeth) == []
|
||||
directive.env.config.autodoc_inherit_docstrings = True
|
||||
assert getdocl('method', Derived.inheritedmeth) == ['Inherited function.']
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_docstring_processing():
|
||||
def process(objtype, name, obj):
|
||||
inst = app.registry.documenters[objtype](directive, name)
|
||||
inst.object = obj
|
||||
inst.fullname = name
|
||||
return list(inst.process_doc(inst.get_doc()))
|
||||
|
||||
class E:
|
||||
def __init__(self):
|
||||
"""Init docstring"""
|
||||
|
||||
# docstring processing by event handler
|
||||
assert process('class', 'bar', E) == ['Init docstring', '', '42', '']
|
||||
|
||||
lid = app.connect('autodoc-process-docstring',
|
||||
cut_lines(1, 1, ['function']))
|
||||
|
||||
def f():
|
||||
"""
|
||||
first line
|
||||
second line
|
||||
third line
|
||||
"""
|
||||
assert process('function', 'f', f) == ['second line', '']
|
||||
app.disconnect(lid)
|
||||
|
||||
lid = app.connect('autodoc-process-docstring', between('---', ['function']))
|
||||
|
||||
def g():
|
||||
"""
|
||||
first line
|
||||
---
|
||||
second line
|
||||
---
|
||||
third line
|
||||
"""
|
||||
assert process('function', 'g', g) == ['second line', '']
|
||||
app.disconnect(lid)
|
||||
|
||||
lid = app.connect('autodoc-process-docstring',
|
||||
between('---', ['function'], exclude=True))
|
||||
|
||||
def h():
|
||||
"""
|
||||
first line
|
||||
---
|
||||
second line
|
||||
---
|
||||
third line
|
||||
"""
|
||||
assert process('function', 'h', h) == ['first line', 'third line', '']
|
||||
app.disconnect(lid)
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_new_documenter(app):
|
||||
@ -565,6 +368,7 @@ def test_new_documenter(app):
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_attrgetter_using():
|
||||
from target import Class
|
||||
from target.inheritance import Derived
|
||||
|
||||
def assert_getter_works(objtype, name, obj, attrs=[], **kw):
|
||||
getattr_spy = []
|
||||
@ -594,7 +398,7 @@ def test_attrgetter_using():
|
||||
assert_getter_works('class', 'target.Class', Class, ['meth'])
|
||||
|
||||
options.inherited_members = True
|
||||
assert_getter_works('class', 'target.Class', Class, ['meth', 'inheritedmeth'])
|
||||
assert_getter_works('class', 'target.inheritance.Derived', Derived, ['inheritedmeth'])
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
@ -697,14 +501,14 @@ def test_autodoc_attributes(app):
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_members(app):
|
||||
# default (no-members)
|
||||
actual = do_autodoc(app, 'class', 'target.Base')
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base')
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Base',
|
||||
]
|
||||
|
||||
# default ALL-members
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'class', 'target.Base', options)
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Base',
|
||||
' .. py:method:: Base.inheritedclassmeth()',
|
||||
@ -714,7 +518,7 @@ def test_autodoc_members(app):
|
||||
|
||||
# default specific-members
|
||||
options = {"members": "inheritedmeth,inheritedstaticmeth"}
|
||||
actual = do_autodoc(app, 'class', 'target.Base', options)
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Base',
|
||||
' .. py:method:: Base.inheritedmeth()',
|
||||
@ -726,7 +530,7 @@ def test_autodoc_members(app):
|
||||
def test_autodoc_exclude_members(app):
|
||||
options = {"members": None,
|
||||
"exclude-members": "inheritedmeth,inheritedstaticmeth"}
|
||||
actual = do_autodoc(app, 'class', 'target.Base', options)
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Base',
|
||||
' .. py:method:: Base.inheritedclassmeth()'
|
||||
@ -735,7 +539,7 @@ def test_autodoc_exclude_members(app):
|
||||
# members vs exclude-members
|
||||
options = {"members": "inheritedmeth",
|
||||
"exclude-members": "inheritedmeth"}
|
||||
actual = do_autodoc(app, 'class', 'target.Base', options)
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Base',
|
||||
]
|
||||
@ -749,7 +553,6 @@ def test_autodoc_undoc_members(app):
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Class(arg)',
|
||||
' .. py:attribute:: Class.attr',
|
||||
' .. py:attribute:: Class.descr',
|
||||
' .. py:attribute:: Class.docattr',
|
||||
' .. py:method:: Class.excludemeth()',
|
||||
' .. py:attribute:: Class.inst_attr_comment',
|
||||
@ -758,7 +561,6 @@ def test_autodoc_undoc_members(app):
|
||||
' .. py:attribute:: Class.mdocattr',
|
||||
' .. py:method:: Class.meth()',
|
||||
' .. py:method:: Class.moore(a, e, f) -> happiness',
|
||||
' .. py:method:: Class.prop',
|
||||
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
|
||||
' .. py:attribute:: Class.skipattr',
|
||||
' .. py:method:: Class.skipmeth()',
|
||||
@ -771,16 +573,11 @@ def test_autodoc_undoc_members(app):
|
||||
def test_autodoc_inherited_members(app):
|
||||
options = {"members": None,
|
||||
"inherited-members": None}
|
||||
actual = do_autodoc(app, 'class', 'target.Class', options)
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options)
|
||||
assert list(filter(lambda l: 'method::' in l, actual)) == [
|
||||
' .. py:method:: Class.excludemeth()',
|
||||
' .. py:method:: Class.inheritedclassmeth()',
|
||||
' .. py:method:: Class.inheritedmeth()',
|
||||
' .. py:method:: Class.inheritedstaticmeth(cls)',
|
||||
' .. py:method:: Class.meth()',
|
||||
' .. py:method:: Class.moore(a, e, f) -> happiness',
|
||||
' .. py:method:: Class.prop',
|
||||
' .. py:method:: Class.skipmeth()'
|
||||
' .. py:method:: Derived.inheritedclassmeth()',
|
||||
' .. py:method:: Derived.inheritedmeth()',
|
||||
' .. py:method:: Derived.inheritedstaticmeth(cls)',
|
||||
]
|
||||
|
||||
|
||||
@ -790,7 +587,7 @@ def test_autodoc_imported_members(app):
|
||||
"imported-members": None,
|
||||
"ignore-module-all": None}
|
||||
actual = do_autodoc(app, 'module', 'target', options)
|
||||
assert '.. py:function:: save_traceback(app)' in actual
|
||||
assert '.. py:function:: save_traceback(app: Sphinx) -> str' in actual
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
@ -825,12 +622,13 @@ def test_autodoc_special_members(app):
|
||||
actual = do_autodoc(app, 'class', 'target.Class', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Class(arg)',
|
||||
' .. py:attribute:: Class.__dict__',
|
||||
' .. py:method:: Class.__init__(arg)',
|
||||
' .. py:attribute:: Class.__module__',
|
||||
' .. py:method:: Class.__special1__()',
|
||||
' .. py:method:: Class.__special2__()',
|
||||
' .. py:attribute:: Class.__weakref__',
|
||||
' .. py:attribute:: Class.attr',
|
||||
' .. py:attribute:: Class.descr',
|
||||
' .. py:attribute:: Class.docattr',
|
||||
' .. py:method:: Class.excludemeth()',
|
||||
' .. py:attribute:: Class.inst_attr_comment',
|
||||
@ -839,7 +637,6 @@ def test_autodoc_special_members(app):
|
||||
' .. py:attribute:: Class.mdocattr',
|
||||
' .. py:method:: Class.meth()',
|
||||
' .. py:method:: Class.moore(a, e, f) -> happiness',
|
||||
' .. py:method:: Class.prop',
|
||||
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
|
||||
' .. py:attribute:: Class.skipattr',
|
||||
' .. py:method:: Class.skipmeth()',
|
||||
@ -863,9 +660,6 @@ def test_autodoc_ignore_module_all(app):
|
||||
actual = do_autodoc(app, 'module', 'target', options)
|
||||
assert list(filter(lambda l: 'class::' in l, actual)) == [
|
||||
'.. py:class:: Class(arg)',
|
||||
'.. py:class:: CustomDataDescriptor(doc)',
|
||||
'.. py:class:: CustomDataDescriptor2(doc)',
|
||||
'.. py:class:: CustomDataDescriptorMeta',
|
||||
'.. py:class:: CustomDict',
|
||||
'.. py:class:: InstAttCls()',
|
||||
'.. py:class:: Outer',
|
||||
@ -887,12 +681,12 @@ def test_autodoc_noindex(app):
|
||||
|
||||
# TODO: :noindex: should be propagated to children of target item.
|
||||
|
||||
actual = do_autodoc(app, 'class', 'target.Base', options)
|
||||
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Base',
|
||||
' :noindex:',
|
||||
' :module: target',
|
||||
' :module: target.inheritance',
|
||||
''
|
||||
]
|
||||
|
||||
@ -960,11 +754,11 @@ def test_autodoc_inner_class(app):
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_classmethod(app):
|
||||
actual = do_autodoc(app, 'method', 'target.Base.inheritedclassmeth')
|
||||
actual = do_autodoc(app, 'method', 'target.inheritance.Base.inheritedclassmeth')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:method:: Base.inheritedclassmeth()',
|
||||
' :module: target',
|
||||
' :module: target.inheritance',
|
||||
' :classmethod:',
|
||||
'',
|
||||
' Inherited class method.',
|
||||
@ -974,11 +768,11 @@ def test_autodoc_classmethod(app):
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_staticmethod(app):
|
||||
actual = do_autodoc(app, 'method', 'target.Base.inheritedstaticmeth')
|
||||
actual = do_autodoc(app, 'method', 'target.inheritance.Base.inheritedstaticmeth')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:method:: Base.inheritedstaticmeth(cls)',
|
||||
' :module: target',
|
||||
' :module: target.inheritance',
|
||||
' :staticmethod:',
|
||||
'',
|
||||
' Inherited static method.',
|
||||
@ -988,14 +782,27 @@ def test_autodoc_staticmethod(app):
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_descriptor(app):
|
||||
actual = do_autodoc(app, 'attribute', 'target.Class.descr')
|
||||
options = {"members": None,
|
||||
"undoc-members": True}
|
||||
actual = do_autodoc(app, 'class', 'target.descriptor.Class', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:attribute:: Class.descr',
|
||||
' :module: target',
|
||||
'.. py:class:: Class',
|
||||
' :module: target.descriptor',
|
||||
'',
|
||||
' Descriptor instance docstring.',
|
||||
' '
|
||||
' ',
|
||||
' .. py:attribute:: Class.descr',
|
||||
' :module: target.descriptor',
|
||||
' ',
|
||||
' Descriptor instance docstring.',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: Class.prop',
|
||||
' :module: target.descriptor',
|
||||
' :property:',
|
||||
' ',
|
||||
' Property.',
|
||||
' '
|
||||
]
|
||||
|
||||
|
||||
@ -1024,14 +831,12 @@ def test_autodoc_member_order(app):
|
||||
actual = do_autodoc(app, 'class', 'target.Class', options)
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Class(arg)',
|
||||
' .. py:attribute:: Class.descr',
|
||||
' .. py:method:: Class.meth()',
|
||||
' .. py:method:: Class.undocmeth()',
|
||||
' .. py:method:: Class.skipmeth()',
|
||||
' .. py:method:: Class.excludemeth()',
|
||||
' .. py:attribute:: Class.skipattr',
|
||||
' .. py:attribute:: Class.attr',
|
||||
' .. py:method:: Class.prop',
|
||||
' .. py:attribute:: Class.docattr',
|
||||
' .. py:attribute:: Class.udocattr',
|
||||
' .. py:attribute:: Class.mdocattr',
|
||||
@ -1059,13 +864,11 @@ def test_autodoc_member_order(app):
|
||||
' .. py:method:: Class.undocmeth()',
|
||||
' .. py:attribute:: Class._private_inst_attr',
|
||||
' .. py:attribute:: Class.attr',
|
||||
' .. py:attribute:: Class.descr',
|
||||
' .. py:attribute:: Class.docattr',
|
||||
' .. py:attribute:: Class.inst_attr_comment',
|
||||
' .. py:attribute:: Class.inst_attr_inline',
|
||||
' .. py:attribute:: Class.inst_attr_string',
|
||||
' .. py:attribute:: Class.mdocattr',
|
||||
' .. py:method:: Class.prop',
|
||||
' .. py:attribute:: Class.skipattr',
|
||||
' .. py:attribute:: Class.udocattr'
|
||||
]
|
||||
@ -1079,7 +882,6 @@ def test_autodoc_member_order(app):
|
||||
'.. py:class:: Class(arg)',
|
||||
' .. py:attribute:: Class._private_inst_attr',
|
||||
' .. py:attribute:: Class.attr',
|
||||
' .. py:attribute:: Class.descr',
|
||||
' .. py:attribute:: Class.docattr',
|
||||
' .. py:method:: Class.excludemeth()',
|
||||
' .. py:attribute:: Class.inst_attr_comment',
|
||||
@ -1088,7 +890,6 @@ def test_autodoc_member_order(app):
|
||||
' .. py:attribute:: Class.mdocattr',
|
||||
' .. py:method:: Class.meth()',
|
||||
' .. py:method:: Class.moore(a, e, f) -> happiness',
|
||||
' .. py:method:: Class.prop',
|
||||
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
|
||||
' .. py:attribute:: Class.skipattr',
|
||||
' .. py:method:: Class.skipmeth()',
|
||||
@ -1128,95 +929,6 @@ def test_autodoc_class_scope(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_docstring_signature(app):
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: DocstringSig',
|
||||
' :module: target',
|
||||
'',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.meth(FOO, BAR=1) -> BAZ',
|
||||
' :module: target',
|
||||
' ',
|
||||
' First line of docstring',
|
||||
' ',
|
||||
' rest of docstring',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.meth2()',
|
||||
' :module: target',
|
||||
' ',
|
||||
' First line, no signature',
|
||||
' Second line followed by indentation::',
|
||||
' ',
|
||||
' indented line',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.prop1',
|
||||
' :module: target',
|
||||
' :property:',
|
||||
' ',
|
||||
' First line of docstring',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.prop2',
|
||||
' :module: target',
|
||||
' :property:',
|
||||
' ',
|
||||
' First line of docstring',
|
||||
' Second line of docstring',
|
||||
' '
|
||||
]
|
||||
|
||||
# disable autodoc_docstring_signature
|
||||
app.config.autodoc_docstring_signature = False
|
||||
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: DocstringSig',
|
||||
' :module: target',
|
||||
'',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.meth()',
|
||||
' :module: target',
|
||||
' ',
|
||||
' meth(FOO, BAR=1) -> BAZ',
|
||||
' First line of docstring',
|
||||
' ',
|
||||
' rest of docstring',
|
||||
' ',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.meth2()',
|
||||
' :module: target',
|
||||
' ',
|
||||
' First line, no signature',
|
||||
' Second line followed by indentation::',
|
||||
' ',
|
||||
' indented line',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.prop1',
|
||||
' :module: target',
|
||||
' :property:',
|
||||
' ',
|
||||
' DocstringSig.prop1(self)',
|
||||
' First line of docstring',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.prop2',
|
||||
' :module: target',
|
||||
' :property:',
|
||||
' ',
|
||||
' First line of docstring',
|
||||
' Second line of docstring',
|
||||
' '
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_class_attributes(app):
|
||||
options = {"members": None,
|
||||
@ -1419,26 +1131,26 @@ def test_enum_class(app):
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_descriptor_class(app):
|
||||
options = {"members": 'CustomDataDescriptor,CustomDataDescriptor2'}
|
||||
actual = do_autodoc(app, 'module', 'target', options)
|
||||
actual = do_autodoc(app, 'module', 'target.descriptor', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target',
|
||||
'.. py:module:: target.descriptor',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: CustomDataDescriptor(doc)',
|
||||
' :module: target',
|
||||
' :module: target.descriptor',
|
||||
'',
|
||||
' Descriptor class docstring.',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: CustomDataDescriptor.meth()',
|
||||
' :module: target',
|
||||
' :module: target.descriptor',
|
||||
' ',
|
||||
' Function.',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: CustomDataDescriptor2(doc)',
|
||||
' :module: target',
|
||||
' :module: target.descriptor',
|
||||
'',
|
||||
' Descriptor class with custom metaclass docstring.',
|
||||
' '
|
||||
@ -1471,57 +1183,6 @@ def test_autofunction_for_method(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_mocked_module_imports(app, warning):
|
||||
# no autodoc_mock_imports
|
||||
options = {"members": 'TestAutodoc,decoratedFunction,func'}
|
||||
actual = do_autodoc(app, 'module', 'target.need_mocks', options)
|
||||
assert list(actual) == []
|
||||
assert "autodoc: failed to import module 'need_mocks'" in warning.getvalue()
|
||||
|
||||
# with autodoc_mock_imports
|
||||
app.config.autodoc_mock_imports = [
|
||||
'missing_module',
|
||||
'missing_package1',
|
||||
'missing_package2',
|
||||
'missing_package3',
|
||||
'sphinx.missing_module4',
|
||||
]
|
||||
|
||||
warning.truncate(0)
|
||||
actual = do_autodoc(app, 'module', 'target.need_mocks', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.need_mocks',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: TestAutodoc',
|
||||
' :module: target.need_mocks',
|
||||
'',
|
||||
' TestAutodoc docstring.',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: TestAutodoc.decoratedMethod()',
|
||||
' :module: target.need_mocks',
|
||||
' ',
|
||||
' TestAutodoc::decoratedMethod docstring',
|
||||
' ',
|
||||
'',
|
||||
'.. py:function:: decoratedFunction()',
|
||||
' :module: target.need_mocks',
|
||||
'',
|
||||
' decoratedFunction docstring',
|
||||
' ',
|
||||
'',
|
||||
'.. py:function:: func(arg: missing_module.Class)',
|
||||
' :module: target.need_mocks',
|
||||
'',
|
||||
' a function takes mocked object as an argument',
|
||||
' '
|
||||
]
|
||||
assert warning.getvalue() == ''
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_abstractmethods():
|
||||
options = {"members": None,
|
||||
@ -1710,214 +1371,6 @@ def test_module_variables():
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_typehints_signature(app):
|
||||
app.config.autodoc_typehints = "signature"
|
||||
|
||||
options = {"members": None,
|
||||
"undoc-members": True}
|
||||
actual = do_autodoc(app, 'module', 'target.typehints', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Math(s: str, o: object = None)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
' ',
|
||||
' .. py:method:: Math.incr(a: int, b: int = 1) -> int',
|
||||
' :module: target.typehints',
|
||||
' ',
|
||||
'',
|
||||
'.. py:function:: incr(a: int, b: int = 1) -> int',
|
||||
' :module: target.typehints',
|
||||
''
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_typehints_none(app):
|
||||
app.config.autodoc_typehints = "none"
|
||||
|
||||
options = {"members": None,
|
||||
"undoc-members": True}
|
||||
actual = do_autodoc(app, 'module', 'target.typehints', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Math(s, o = None)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
' ',
|
||||
' .. py:method:: Math.incr(a, b = 1) -> int',
|
||||
' :module: target.typehints',
|
||||
' ',
|
||||
'',
|
||||
'.. py:function:: incr(a, b = 1) -> int',
|
||||
' :module: target.typehints',
|
||||
''
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_default_options(app):
|
||||
# no settings
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' not in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: target.CustomIter' not in actual
|
||||
actual = do_autodoc(app, 'module', 'target')
|
||||
assert '.. py:function:: save_traceback(app)' not in actual
|
||||
|
||||
# with :members:
|
||||
app.config.autodoc_default_options = {'members': None}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
|
||||
# with :members: = True
|
||||
app.config.autodoc_default_options = {'members': True}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
|
||||
# with :members: and :undoc-members:
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'undoc-members': None,
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' in actual
|
||||
|
||||
# with :special-members:
|
||||
# Note that :members: must be *on* for :special-members: to work.
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'special-members': None
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: CustomIter.__init__()' in actual
|
||||
assert ' Create a new `CustomIter`.' in actual
|
||||
assert ' .. py:method:: CustomIter.__iter__()' in actual
|
||||
assert ' Iterate squares of each value.' in actual
|
||||
if not IS_PYPY:
|
||||
assert ' .. py:attribute:: CustomIter.__weakref__' in actual
|
||||
assert ' list of weak references to the object (if defined)' in actual
|
||||
|
||||
# :exclude-members: None - has no effect. Unlike :members:,
|
||||
# :special-members:, etc. where None == "include all", here None means
|
||||
# "no/false/off".
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'exclude-members': None,
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'special-members': None,
|
||||
'exclude-members': None,
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: CustomIter.__init__()' in actual
|
||||
assert ' Create a new `CustomIter`.' in actual
|
||||
assert ' .. py:method:: CustomIter.__iter__()' in actual
|
||||
assert ' Iterate squares of each value.' in actual
|
||||
if not IS_PYPY:
|
||||
assert ' .. py:attribute:: CustomIter.__weakref__' in actual
|
||||
assert ' list of weak references to the object (if defined)' in actual
|
||||
assert ' .. py:method:: CustomIter.snafucate()' in actual
|
||||
assert ' Makes this snafucated.' in actual
|
||||
|
||||
# with :imported-members:
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'imported-members': None,
|
||||
'ignore-module-all': None,
|
||||
}
|
||||
actual = do_autodoc(app, 'module', 'target')
|
||||
assert '.. py:function:: save_traceback(app)' in actual
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_default_options_with_values(app):
|
||||
# with :members:
|
||||
app.config.autodoc_default_options = {'members': 'val1,val2'}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val2' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val3' not in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
|
||||
# with :member-order:
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'member-order': 'bysource',
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.Class')
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Class(arg)',
|
||||
' .. py:attribute:: Class.descr',
|
||||
' .. py:method:: Class.meth()',
|
||||
' .. py:method:: Class.skipmeth()',
|
||||
' .. py:method:: Class.excludemeth()',
|
||||
' .. py:attribute:: Class.attr',
|
||||
' .. py:method:: Class.prop',
|
||||
' .. py:attribute:: Class.docattr',
|
||||
' .. py:attribute:: Class.udocattr',
|
||||
' .. py:attribute:: Class.mdocattr',
|
||||
' .. py:method:: Class.moore(a, e, f) -> happiness',
|
||||
' .. py:attribute:: Class.inst_attr_inline',
|
||||
' .. py:attribute:: Class.inst_attr_comment',
|
||||
' .. py:attribute:: Class.inst_attr_string',
|
||||
]
|
||||
|
||||
# with :special-members:
|
||||
app.config.autodoc_default_options = {
|
||||
'special-members': '__init__,__iter__',
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: CustomIter.__init__()' in actual
|
||||
assert ' Create a new `CustomIter`.' in actual
|
||||
assert ' .. py:method:: CustomIter.__iter__()' in actual
|
||||
assert ' Iterate squares of each value.' in actual
|
||||
if not IS_PYPY:
|
||||
assert ' .. py:attribute:: CustomIter.__weakref__' not in actual
|
||||
assert ' list of weak references to the object (if defined)' not in actual
|
||||
|
||||
# with :exclude-members:
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'exclude-members': 'val1'
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' not in actual
|
||||
assert ' .. py:attribute:: EnumCls.val2' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val3' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'special-members': None,
|
||||
'exclude-members': '__weakref__,snafucate',
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: CustomIter.__init__()' in actual
|
||||
assert ' Create a new `CustomIter`.' in actual
|
||||
assert ' .. py:method:: CustomIter.__iter__()' in actual
|
||||
assert ' Iterate squares of each value.' in actual
|
||||
if not IS_PYPY:
|
||||
assert ' .. py:attribute:: CustomIter.__weakref__' not in actual
|
||||
assert ' list of weak references to the object (if defined)' not in actual
|
||||
assert ' .. py:method:: CustomIter.snafucate()' not in actual
|
||||
assert ' Makes this snafucated.' not in actual
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='pycode-egg')
|
||||
def test_autodoc_for_egged_code(app):
|
||||
options = {"members": None,
|
||||
|
@ -451,8 +451,10 @@ def test_html_download(app):
|
||||
@pytest.mark.sphinx('html', testroot='roles-download')
|
||||
def test_html_download_role(app, status, warning):
|
||||
app.build()
|
||||
digest = md5((app.srcdir / 'dummy.dat').encode()).hexdigest()
|
||||
digest = md5(b'dummy.dat').hexdigest()
|
||||
assert (app.outdir / '_downloads' / digest / 'dummy.dat').exists()
|
||||
digest_another = md5(b'another/dummy.dat').hexdigest()
|
||||
assert (app.outdir / '_downloads' / digest_another / 'dummy.dat').exists()
|
||||
|
||||
content = (app.outdir / 'index.html').text()
|
||||
assert (('<li><p><a class="reference download internal" download="" '
|
||||
@ -460,6 +462,11 @@ def test_html_download_role(app, status, warning):
|
||||
'<code class="xref download docutils literal notranslate">'
|
||||
'<span class="pre">dummy.dat</span></code></a></p></li>' % digest)
|
||||
in content)
|
||||
assert (('<li><p><a class="reference download internal" download="" '
|
||||
'href="_downloads/%s/dummy.dat">'
|
||||
'<code class="xref download docutils literal notranslate">'
|
||||
'<span class="pre">another/dummy.dat</span></code></a></p></li>' %
|
||||
digest_another) in content)
|
||||
assert ('<li><p><code class="xref download docutils literal notranslate">'
|
||||
'<span class="pre">not_found.dat</span></code></p></li>' in content)
|
||||
assert ('<li><p><a class="reference download external" download="" '
|
||||
|
@ -467,7 +467,8 @@ def test_package_file(tempdir):
|
||||
outdir = path(tempdir)
|
||||
(outdir / 'testpkg').makedirs()
|
||||
(outdir / 'testpkg' / '__init__.py').write_text('')
|
||||
(outdir / 'testpkg' / 'example.py').write_text('')
|
||||
(outdir / 'testpkg' / 'hello.py').write_text('')
|
||||
(outdir / 'testpkg' / 'world.py').write_text('')
|
||||
(outdir / 'testpkg' / 'subpkg').makedirs()
|
||||
(outdir / 'testpkg' / 'subpkg' / '__init__.py').write_text('')
|
||||
apidoc_main(['-o', tempdir, tempdir / 'testpkg'])
|
||||
@ -488,10 +489,18 @@ def test_package_file(tempdir):
|
||||
"Submodules\n"
|
||||
"----------\n"
|
||||
"\n"
|
||||
"testpkg.example module\n"
|
||||
"----------------------\n"
|
||||
"testpkg.hello module\n"
|
||||
"--------------------\n"
|
||||
"\n"
|
||||
".. automodule:: testpkg.example\n"
|
||||
".. automodule:: testpkg.hello\n"
|
||||
" :members:\n"
|
||||
" :undoc-members:\n"
|
||||
" :show-inheritance:\n"
|
||||
"\n"
|
||||
"testpkg.world module\n"
|
||||
"--------------------\n"
|
||||
"\n"
|
||||
".. automodule:: testpkg.world\n"
|
||||
" :members:\n"
|
||||
" :undoc-members:\n"
|
||||
" :show-inheritance:\n"
|
||||
|
619
tests/test_ext_autodoc_configs.py
Normal file
619
tests/test_ext_autodoc_configs.py
Normal file
@ -0,0 +1,619 @@
|
||||
"""
|
||||
test_ext_autodoc_configs
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test the autodoc extension. This tests mainly for config variables
|
||||
|
||||
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import platform
|
||||
|
||||
import pytest
|
||||
|
||||
from test_autodoc import do_autodoc
|
||||
|
||||
IS_PYPY = platform.python_implementation() == 'PyPy'
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoclass_content_class(app):
|
||||
app.config.autoclass_content = 'class'
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.autoclass_content', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.autoclass_content',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: A',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, no __new__',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: B()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having __init__(no docstring), no __new__',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: C()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having __init__, no __new__',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: D',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, __new__(no docstring)',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: E',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, __new__',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: F()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having both __init__ and __new__',
|
||||
' '
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoclass_content_init(app):
|
||||
app.config.autoclass_content = 'init'
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.autoclass_content', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.autoclass_content',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: A',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, no __new__',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: B()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having __init__(no docstring), no __new__',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: C()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' __init__ docstring',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: D',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, __new__(no docstring)',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: E',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' __new__ docstring',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: F()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' __init__ docstring',
|
||||
' '
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoclass_content_both(app):
|
||||
app.config.autoclass_content = 'both'
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.autoclass_content', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.autoclass_content',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: A',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, no __new__',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: B()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having __init__(no docstring), no __new__',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: C()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having __init__, no __new__',
|
||||
' ',
|
||||
' __init__ docstring',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: D',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, __new__(no docstring)',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: E',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having no __init__, __new__',
|
||||
' ',
|
||||
' __new__ docstring',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: F()',
|
||||
' :module: target.autoclass_content',
|
||||
'',
|
||||
' A class having both __init__ and __new__',
|
||||
' ',
|
||||
' __init__ docstring',
|
||||
' '
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_inherit_docstrings(app):
|
||||
assert app.config.autodoc_inherit_docstrings is True # default
|
||||
actual = do_autodoc(app, 'method', 'target.inheritance.Derived.inheritedmeth')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:method:: Derived.inheritedmeth()',
|
||||
' :module: target.inheritance',
|
||||
'',
|
||||
' Inherited function.',
|
||||
' '
|
||||
]
|
||||
|
||||
# disable autodoc_inherit_docstrings
|
||||
app.config.autodoc_inherit_docstrings = False
|
||||
actual = do_autodoc(app, 'method', 'target.inheritance.Derived.inheritedmeth')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:method:: Derived.inheritedmeth()',
|
||||
' :module: target.inheritance',
|
||||
''
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_docstring_signature(app):
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: DocstringSig',
|
||||
' :module: target',
|
||||
'',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.meth(FOO, BAR=1) -> BAZ',
|
||||
' :module: target',
|
||||
' ',
|
||||
' First line of docstring',
|
||||
' ',
|
||||
' rest of docstring',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.meth2()',
|
||||
' :module: target',
|
||||
' ',
|
||||
' First line, no signature',
|
||||
' Second line followed by indentation::',
|
||||
' ',
|
||||
' indented line',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.prop1',
|
||||
' :module: target',
|
||||
' :property:',
|
||||
' ',
|
||||
' First line of docstring',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.prop2',
|
||||
' :module: target',
|
||||
' :property:',
|
||||
' ',
|
||||
' First line of docstring',
|
||||
' Second line of docstring',
|
||||
' '
|
||||
]
|
||||
|
||||
# disable autodoc_docstring_signature
|
||||
app.config.autodoc_docstring_signature = False
|
||||
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: DocstringSig',
|
||||
' :module: target',
|
||||
'',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.meth()',
|
||||
' :module: target',
|
||||
' ',
|
||||
' meth(FOO, BAR=1) -> BAZ',
|
||||
' First line of docstring',
|
||||
' ',
|
||||
' rest of docstring',
|
||||
' ',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.meth2()',
|
||||
' :module: target',
|
||||
' ',
|
||||
' First line, no signature',
|
||||
' Second line followed by indentation::',
|
||||
' ',
|
||||
' indented line',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.prop1',
|
||||
' :module: target',
|
||||
' :property:',
|
||||
' ',
|
||||
' DocstringSig.prop1(self)',
|
||||
' First line of docstring',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: DocstringSig.prop2',
|
||||
' :module: target',
|
||||
' :property:',
|
||||
' ',
|
||||
' First line of docstring',
|
||||
' Second line of docstring',
|
||||
' '
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoclass_content_and_docstring_signature_class(app):
|
||||
app.config.autoclass_content = 'class'
|
||||
options = {"members": None,
|
||||
"undoc-members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.docstring_signature', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: A(foo, bar)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: B(foo, bar)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: C(foo, bar)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: D()',
|
||||
' :module: target.docstring_signature',
|
||||
''
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoclass_content_and_docstring_signature_init(app):
|
||||
app.config.autoclass_content = 'init'
|
||||
options = {"members": None,
|
||||
"undoc-members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.docstring_signature', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: A(foo, bar)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: B(foo, bar, baz)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: C(foo, bar, baz)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: D(foo, bar, baz)',
|
||||
' :module: target.docstring_signature',
|
||||
''
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoclass_content_and_docstring_signature_both(app):
|
||||
app.config.autoclass_content = 'both'
|
||||
options = {"members": None,
|
||||
"undoc-members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.docstring_signature', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: A(foo, bar)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: B(foo, bar)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
' B(foo, bar, baz)',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: C(foo, bar)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
' C(foo, bar, baz)',
|
||||
' ',
|
||||
'',
|
||||
'.. py:class:: D(foo, bar, baz)',
|
||||
' :module: target.docstring_signature',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_mocked_module_imports(app, warning):
|
||||
# no autodoc_mock_imports
|
||||
options = {"members": 'TestAutodoc,decoratedFunction,func'}
|
||||
actual = do_autodoc(app, 'module', 'target.need_mocks', options)
|
||||
assert list(actual) == []
|
||||
assert "autodoc: failed to import module 'need_mocks'" in warning.getvalue()
|
||||
|
||||
# with autodoc_mock_imports
|
||||
app.config.autodoc_mock_imports = [
|
||||
'missing_module',
|
||||
'missing_package1',
|
||||
'missing_package2',
|
||||
'missing_package3',
|
||||
'sphinx.missing_module4',
|
||||
]
|
||||
|
||||
warning.truncate(0)
|
||||
actual = do_autodoc(app, 'module', 'target.need_mocks', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.need_mocks',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: TestAutodoc',
|
||||
' :module: target.need_mocks',
|
||||
'',
|
||||
' TestAutodoc docstring.',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: TestAutodoc.decoratedMethod()',
|
||||
' :module: target.need_mocks',
|
||||
' ',
|
||||
' TestAutodoc::decoratedMethod docstring',
|
||||
' ',
|
||||
'',
|
||||
'.. py:function:: decoratedFunction()',
|
||||
' :module: target.need_mocks',
|
||||
'',
|
||||
' decoratedFunction docstring',
|
||||
' ',
|
||||
'',
|
||||
'.. py:function:: func(arg: missing_module.Class)',
|
||||
' :module: target.need_mocks',
|
||||
'',
|
||||
' a function takes mocked object as an argument',
|
||||
' '
|
||||
]
|
||||
assert warning.getvalue() == ''
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_typehints_signature(app):
|
||||
app.config.autodoc_typehints = "signature"
|
||||
|
||||
options = {"members": None,
|
||||
"undoc-members": True}
|
||||
actual = do_autodoc(app, 'module', 'target.typehints', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Math(s: str, o: object = None)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
' ',
|
||||
' .. py:method:: Math.incr(a: int, b: int = 1) -> int',
|
||||
' :module: target.typehints',
|
||||
' ',
|
||||
'',
|
||||
'.. py:function:: incr(a: int, b: int = 1) -> int',
|
||||
' :module: target.typehints',
|
||||
''
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_typehints_none(app):
|
||||
app.config.autodoc_typehints = "none"
|
||||
|
||||
options = {"members": None,
|
||||
"undoc-members": True}
|
||||
actual = do_autodoc(app, 'module', 'target.typehints', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.typehints',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Math(s, o = None)',
|
||||
' :module: target.typehints',
|
||||
'',
|
||||
' ',
|
||||
' .. py:method:: Math.incr(a, b = 1) -> int',
|
||||
' :module: target.typehints',
|
||||
' ',
|
||||
'',
|
||||
'.. py:function:: incr(a, b = 1) -> int',
|
||||
' :module: target.typehints',
|
||||
''
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_default_options(app):
|
||||
# no settings
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' not in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: target.CustomIter' not in actual
|
||||
actual = do_autodoc(app, 'module', 'target')
|
||||
assert '.. py:function:: save_traceback(app)' not in actual
|
||||
|
||||
# with :members:
|
||||
app.config.autodoc_default_options = {'members': None}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
|
||||
# with :members: = True
|
||||
app.config.autodoc_default_options = {'members': True}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
|
||||
# with :members: and :undoc-members:
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'undoc-members': None,
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' in actual
|
||||
|
||||
# with :special-members:
|
||||
# Note that :members: must be *on* for :special-members: to work.
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'special-members': None
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: CustomIter.__init__()' in actual
|
||||
assert ' Create a new `CustomIter`.' in actual
|
||||
assert ' .. py:method:: CustomIter.__iter__()' in actual
|
||||
assert ' Iterate squares of each value.' in actual
|
||||
if not IS_PYPY:
|
||||
assert ' .. py:attribute:: CustomIter.__weakref__' in actual
|
||||
assert ' list of weak references to the object (if defined)' in actual
|
||||
|
||||
# :exclude-members: None - has no effect. Unlike :members:,
|
||||
# :special-members:, etc. where None == "include all", here None means
|
||||
# "no/false/off".
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'exclude-members': None,
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'special-members': None,
|
||||
'exclude-members': None,
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: CustomIter.__init__()' in actual
|
||||
assert ' Create a new `CustomIter`.' in actual
|
||||
assert ' .. py:method:: CustomIter.__iter__()' in actual
|
||||
assert ' Iterate squares of each value.' in actual
|
||||
if not IS_PYPY:
|
||||
assert ' .. py:attribute:: CustomIter.__weakref__' in actual
|
||||
assert ' list of weak references to the object (if defined)' in actual
|
||||
assert ' .. py:method:: CustomIter.snafucate()' in actual
|
||||
assert ' Makes this snafucated.' in actual
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_default_options_with_values(app):
|
||||
# with :members:
|
||||
app.config.autodoc_default_options = {'members': 'val1,val2'}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val2' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val3' not in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
|
||||
# with :member-order:
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'member-order': 'bysource',
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.Class')
|
||||
assert list(filter(lambda l: '::' in l, actual)) == [
|
||||
'.. py:class:: Class(arg)',
|
||||
' .. py:method:: Class.meth()',
|
||||
' .. py:method:: Class.skipmeth()',
|
||||
' .. py:method:: Class.excludemeth()',
|
||||
' .. py:attribute:: Class.attr',
|
||||
' .. py:attribute:: Class.docattr',
|
||||
' .. py:attribute:: Class.udocattr',
|
||||
' .. py:attribute:: Class.mdocattr',
|
||||
' .. py:method:: Class.moore(a, e, f) -> happiness',
|
||||
' .. py:attribute:: Class.inst_attr_inline',
|
||||
' .. py:attribute:: Class.inst_attr_comment',
|
||||
' .. py:attribute:: Class.inst_attr_string',
|
||||
]
|
||||
|
||||
# with :special-members:
|
||||
app.config.autodoc_default_options = {
|
||||
'special-members': '__init__,__iter__',
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: CustomIter.__init__()' in actual
|
||||
assert ' Create a new `CustomIter`.' in actual
|
||||
assert ' .. py:method:: CustomIter.__iter__()' in actual
|
||||
assert ' Iterate squares of each value.' in actual
|
||||
if not IS_PYPY:
|
||||
assert ' .. py:attribute:: CustomIter.__weakref__' not in actual
|
||||
assert ' list of weak references to the object (if defined)' not in actual
|
||||
|
||||
# with :exclude-members:
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'exclude-members': 'val1'
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
|
||||
assert ' .. py:attribute:: EnumCls.val1' not in actual
|
||||
assert ' .. py:attribute:: EnumCls.val2' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val3' in actual
|
||||
assert ' .. py:attribute:: EnumCls.val4' not in actual
|
||||
app.config.autodoc_default_options = {
|
||||
'members': None,
|
||||
'special-members': None,
|
||||
'exclude-members': '__weakref__,snafucate',
|
||||
}
|
||||
actual = do_autodoc(app, 'class', 'target.CustomIter')
|
||||
assert ' .. py:method:: CustomIter.__init__()' in actual
|
||||
assert ' Create a new `CustomIter`.' in actual
|
||||
assert ' .. py:method:: CustomIter.__iter__()' in actual
|
||||
assert ' Iterate squares of each value.' in actual
|
||||
if not IS_PYPY:
|
||||
assert ' .. py:attribute:: CustomIter.__weakref__' not in actual
|
||||
assert ' list of weak references to the object (if defined)' not in actual
|
||||
assert ' .. py:method:: CustomIter.snafucate()' not in actual
|
||||
assert ' Makes this snafucated.' not in actual
|
81
tests/test_ext_autodoc_events.py
Normal file
81
tests/test_ext_autodoc_events.py
Normal file
@ -0,0 +1,81 @@
|
||||
"""
|
||||
test_ext_autodoc_events
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test the autodoc extension. This tests mainly for autodoc events
|
||||
|
||||
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from sphinx.ext.autodoc import between, cut_lines
|
||||
from test_autodoc import do_autodoc
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_process_docstring(app):
|
||||
def on_process_docstring(app, what, name, obj, options, lines):
|
||||
lines.clear()
|
||||
lines.append('my docstring')
|
||||
|
||||
app.connect('autodoc-process-docstring', on_process_docstring)
|
||||
|
||||
actual = do_autodoc(app, 'function', 'target.process_docstring.func')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: func()',
|
||||
' :module: target.process_docstring',
|
||||
'',
|
||||
' my docstring'
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_cut_lines(app):
|
||||
app.connect('autodoc-process-docstring',
|
||||
cut_lines(2, 2, ['function']))
|
||||
|
||||
actual = do_autodoc(app, 'function', 'target.process_docstring.func')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: func()',
|
||||
' :module: target.process_docstring',
|
||||
'',
|
||||
' second line',
|
||||
' '
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_between(app):
|
||||
app.connect('autodoc-process-docstring',
|
||||
between('---', ['function']))
|
||||
|
||||
actual = do_autodoc(app, 'function', 'target.process_docstring.func')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: func()',
|
||||
' :module: target.process_docstring',
|
||||
'',
|
||||
' second line',
|
||||
' '
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_between_exclude(app):
|
||||
app.connect('autodoc-process-docstring',
|
||||
between('---', ['function'], exclude=True))
|
||||
|
||||
actual = do_autodoc(app, 'function', 'target.process_docstring.func')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: func()',
|
||||
' :module: target.process_docstring',
|
||||
'',
|
||||
' first line',
|
||||
' third line',
|
||||
' '
|
||||
]
|
@ -10,7 +10,7 @@
|
||||
|
||||
import sys
|
||||
from io import StringIO
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from docutils import nodes
|
||||
@ -19,6 +19,7 @@ from sphinx import addnodes
|
||||
from sphinx.ext.autosummary import (
|
||||
autosummary_table, autosummary_toc, mangle_signature, import_by_name, extract_summary
|
||||
)
|
||||
from sphinx.ext.autosummary.generate import generate_autosummary_docs
|
||||
from sphinx.testing.util import assert_node, etree_parse
|
||||
from sphinx.util.docutils import new_document
|
||||
|
||||
@ -286,3 +287,18 @@ def test_autosummary_imported_members(app, status, warning):
|
||||
' \n' in module)
|
||||
finally:
|
||||
sys.modules.pop('autosummary_dummy_package', None)
|
||||
|
||||
|
||||
@pytest.mark.sphinx(testroot='ext-autodoc')
|
||||
def test_generate_autosummary_docs_property(app):
|
||||
with patch('sphinx.ext.autosummary.generate.find_autosummary_in_files') as mock:
|
||||
mock.return_value = [('target.methods.Base.prop', 'prop', None)]
|
||||
generate_autosummary_docs([], output_dir=app.srcdir, builder=app.builder, app=app)
|
||||
|
||||
content = (app.srcdir / 'target.methods.Base.prop.rst').text()
|
||||
assert content == ("target.methods.Base.prop\n"
|
||||
"========================\n"
|
||||
"\n"
|
||||
".. currentmodule:: target.methods\n"
|
||||
"\n"
|
||||
".. autoproperty:: Base.prop")
|
||||
|
Loading…
Reference in New Issue
Block a user