mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Add type-check annotations to sphinx.ext
This commit is contained in:
parent
23b1c3d5f2
commit
0d1875e2b5
@ -19,6 +19,7 @@ from types import FunctionType, BuiltinFunctionType, MethodType
|
||||
|
||||
from six import PY2, iterkeys, iteritems, itervalues, text_type, class_types, \
|
||||
string_types, StringIO
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.utils import assemble_option_dict
|
||||
from docutils.statemachine import ViewList
|
||||
@ -34,11 +35,18 @@ from sphinx.util.inspect import getargspec, isdescriptor, safe_getmembers, \
|
||||
safe_getattr, object_description, is_builtin_class_method, isenumattribute
|
||||
from sphinx.util.docstrings import prepare_docstring
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Iterator, Sequence, Tuple, Type, Union # NOQA
|
||||
from types import ModuleType # NOQA
|
||||
from docutils.utils import Reporter # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
try:
|
||||
if sys.version_info >= (3,):
|
||||
import typing
|
||||
else:
|
||||
typing = None
|
||||
typing = None # type: ignore
|
||||
except ImportError:
|
||||
typing = None
|
||||
|
||||
@ -56,28 +64,33 @@ py_ext_sig_re = re.compile(
|
||||
class DefDict(dict):
|
||||
"""A dict that returns a default on nonexisting keys."""
|
||||
def __init__(self, default):
|
||||
# type: (Any) -> None
|
||||
dict.__init__(self)
|
||||
self.default = default
|
||||
|
||||
def __getitem__(self, key):
|
||||
# type: (Any) -> Any
|
||||
try:
|
||||
return dict.__getitem__(self, key)
|
||||
except KeyError:
|
||||
return self.default
|
||||
|
||||
def __bool__(self):
|
||||
# type: () -> bool
|
||||
# docutils check "if option_spec"
|
||||
return True
|
||||
__nonzero__ = __bool__ # for python2 compatibility
|
||||
|
||||
|
||||
def identity(x):
|
||||
# type: (Any) -> Any
|
||||
return x
|
||||
|
||||
|
||||
class Options(dict):
|
||||
"""A dict/attribute hybrid that returns None on nonexisting keys."""
|
||||
def __getattr__(self, name):
|
||||
# type: (unicode) -> Any
|
||||
try:
|
||||
return self[name.replace('_', '-')]
|
||||
except KeyError:
|
||||
@ -90,22 +103,26 @@ class _MockModule(object):
|
||||
__path__ = '/dev/null'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.__all__ = []
|
||||
# type: (Any, Any) -> None
|
||||
self.__all__ = [] # type: List[str]
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# type: (Any, Any) -> _MockModule
|
||||
if args and type(args[0]) in [FunctionType, MethodType]:
|
||||
# Appears to be a decorator, pass through unchanged
|
||||
return args[0]
|
||||
return _MockModule()
|
||||
|
||||
def _append_submodule(self, submod):
|
||||
# type: (str) -> None
|
||||
self.__all__.append(submod)
|
||||
|
||||
@classmethod
|
||||
def __getattr__(cls, name):
|
||||
# type: (unicode) -> Any
|
||||
if name[0] == name[0].upper():
|
||||
# Not very good, we assume Uppercase names are classes...
|
||||
mocktype = type(name, (), {})
|
||||
mocktype = type(name, (), {}) # type: ignore
|
||||
mocktype.__module__ = __name__
|
||||
return mocktype
|
||||
else:
|
||||
@ -113,15 +130,16 @@ class _MockModule(object):
|
||||
|
||||
|
||||
def mock_import(modname):
|
||||
# type: (str) -> None
|
||||
if '.' in modname:
|
||||
pkg, _n, mods = modname.rpartition('.')
|
||||
mock_import(pkg)
|
||||
if isinstance(sys.modules[pkg], _MockModule):
|
||||
sys.modules[pkg]._append_submodule(mods)
|
||||
sys.modules[pkg]._append_submodule(mods) # type: ignore
|
||||
|
||||
if modname not in sys.modules:
|
||||
mod = _MockModule()
|
||||
sys.modules[modname] = mod
|
||||
sys.modules[modname] = mod # type: ignore
|
||||
|
||||
|
||||
ALL = object()
|
||||
@ -129,6 +147,7 @@ INSTANCEATTR = object()
|
||||
|
||||
|
||||
def members_option(arg):
|
||||
# type: (Any) -> Union[object, List[unicode]]
|
||||
"""Used to convert the :members: option to auto directives."""
|
||||
if arg is None:
|
||||
return ALL
|
||||
@ -136,6 +155,7 @@ def members_option(arg):
|
||||
|
||||
|
||||
def members_set_option(arg):
|
||||
# type: (Any) -> Union[object, Set[unicode]]
|
||||
"""Used to convert the :members: option to auto directives."""
|
||||
if arg is None:
|
||||
return ALL
|
||||
@ -146,6 +166,7 @@ SUPPRESS = object()
|
||||
|
||||
|
||||
def annotation_option(arg):
|
||||
# type: (Any) -> Any
|
||||
if arg is None:
|
||||
# suppress showing the representation of the object
|
||||
return SUPPRESS
|
||||
@ -154,6 +175,7 @@ def annotation_option(arg):
|
||||
|
||||
|
||||
def bool_option(arg):
|
||||
# type: (Any) -> bool
|
||||
"""Used to convert flag options to auto directives. (Instead of
|
||||
directives.flag(), which returns None).
|
||||
"""
|
||||
@ -166,13 +188,16 @@ class AutodocReporter(object):
|
||||
and line number to a system message, as recorded in a ViewList.
|
||||
"""
|
||||
def __init__(self, viewlist, reporter):
|
||||
# type: (ViewList, Reporter) -> None
|
||||
self.viewlist = viewlist
|
||||
self.reporter = reporter
|
||||
|
||||
def __getattr__(self, name):
|
||||
# type: (unicode) -> Any
|
||||
return getattr(self.reporter, name)
|
||||
|
||||
def system_message(self, level, message, *children, **kwargs):
|
||||
# type: (int, unicode, Any, Any) -> nodes.system_message
|
||||
if 'line' in kwargs and 'source' not in kwargs:
|
||||
try:
|
||||
source, line = self.viewlist.items[kwargs['line']]
|
||||
@ -185,25 +210,31 @@ class AutodocReporter(object):
|
||||
*children, **kwargs)
|
||||
|
||||
def debug(self, *args, **kwargs):
|
||||
# type: (Any, Any) -> nodes.system_message
|
||||
if self.reporter.debug_flag:
|
||||
return self.system_message(0, *args, **kwargs)
|
||||
|
||||
def info(self, *args, **kwargs):
|
||||
# type: (Any, Any) -> nodes.system_message
|
||||
return self.system_message(1, *args, **kwargs)
|
||||
|
||||
def warning(self, *args, **kwargs):
|
||||
# type: (Any, Any) -> nodes.system_message
|
||||
return self.system_message(2, *args, **kwargs)
|
||||
|
||||
def error(self, *args, **kwargs):
|
||||
# type: (Any, Any) -> nodes.system_message
|
||||
return self.system_message(3, *args, **kwargs)
|
||||
|
||||
def severe(self, *args, **kwargs):
|
||||
# type: (Any, Any) -> nodes.system_message
|
||||
return self.system_message(4, *args, **kwargs)
|
||||
|
||||
|
||||
# Some useful event listener factories for autodoc-process-docstring.
|
||||
|
||||
def cut_lines(pre, post=0, what=None):
|
||||
# type: (int, int, unicode) -> Callable
|
||||
"""Return a listener that removes the first *pre* and last *post*
|
||||
lines of every docstring. If *what* is a sequence of strings,
|
||||
only docstrings of a type in *what* will be processed.
|
||||
@ -216,6 +247,7 @@ def cut_lines(pre, post=0, what=None):
|
||||
This can (and should) be used in place of :confval:`automodule_skip_lines`.
|
||||
"""
|
||||
def process(app, what_, name, obj, options, lines):
|
||||
# type: (Sphinx, unicode, unicode, Any, Any, List[unicode]) -> None
|
||||
if what and what_ not in what:
|
||||
return
|
||||
del lines[:pre]
|
||||
@ -231,6 +263,7 @@ def cut_lines(pre, post=0, what=None):
|
||||
|
||||
|
||||
def between(marker, what=None, keepempty=False, exclude=False):
|
||||
# type: (unicode, Sequence[unicode], bool, bool) -> Callable
|
||||
"""Return a listener that either keeps, or if *exclude* is True excludes,
|
||||
lines between lines that match the *marker* regular expression. If no line
|
||||
matches, the resulting docstring would be empty, so no change will be made
|
||||
@ -242,6 +275,7 @@ def between(marker, what=None, keepempty=False, exclude=False):
|
||||
marker_re = re.compile(marker)
|
||||
|
||||
def process(app, what_, name, obj, options, lines):
|
||||
# type: (Sphinx, unicode, unicode, Any, Any, List[unicode]) -> None
|
||||
if what and what_ not in what:
|
||||
return
|
||||
deleted = 0
|
||||
@ -265,6 +299,7 @@ def between(marker, what=None, keepempty=False, exclude=False):
|
||||
|
||||
|
||||
def format_annotation(annotation):
|
||||
# type: (Any) -> str
|
||||
"""Return formatted representation of a type annotation.
|
||||
|
||||
Show qualified names for types and additional details for types from
|
||||
@ -272,18 +307,18 @@ def format_annotation(annotation):
|
||||
|
||||
Displaying complex types from ``typing`` relies on its private API.
|
||||
"""
|
||||
if typing and isinstance(annotation, typing.TypeVar):
|
||||
if typing and isinstance(annotation, typing.TypeVar): # type: ignore
|
||||
return annotation.__name__
|
||||
if annotation == Ellipsis:
|
||||
return '...'
|
||||
if not isinstance(annotation, type):
|
||||
return repr(annotation)
|
||||
|
||||
qualified_name = (annotation.__module__ + '.' + annotation.__qualname__
|
||||
qualified_name = (annotation.__module__ + '.' + annotation.__qualname__ # type: ignore
|
||||
if annotation else repr(annotation))
|
||||
|
||||
if annotation.__module__ == 'builtins':
|
||||
return annotation.__qualname__
|
||||
return annotation.__qualname__ # type: ignore
|
||||
elif typing:
|
||||
if hasattr(typing, 'GenericMeta') and \
|
||||
isinstance(annotation, typing.GenericMeta):
|
||||
@ -344,6 +379,7 @@ def format_annotation(annotation):
|
||||
|
||||
def formatargspec(function, args, varargs=None, varkw=None, defaults=None,
|
||||
kwonlyargs=(), kwonlydefaults={}, annotations={}):
|
||||
# type: (Callable, Tuple[str, ...], str, str, Any, Tuple, Dict, Dict[str, Any]) -> str
|
||||
"""Return a string representation of an ``inspect.FullArgSpec`` tuple.
|
||||
|
||||
An enhanced version of ``inspect.formatargspec()`` that handles typing
|
||||
@ -351,18 +387,20 @@ def formatargspec(function, args, varargs=None, varkw=None, defaults=None,
|
||||
"""
|
||||
|
||||
def format_arg_with_annotation(name):
|
||||
# type: (str) -> str
|
||||
if name in annotations:
|
||||
return '%s: %s' % (name, format_annotation(get_annotation(name)))
|
||||
return name
|
||||
|
||||
def get_annotation(name):
|
||||
# type: (str) -> str
|
||||
value = annotations[name]
|
||||
if isinstance(value, string_types):
|
||||
return introspected_hints.get(name, value)
|
||||
else:
|
||||
return value
|
||||
|
||||
introspected_hints = (typing.get_type_hints(function)
|
||||
introspected_hints = (typing.get_type_hints(function) # type: ignore
|
||||
if typing and hasattr(function, '__code__') else {})
|
||||
|
||||
fd = StringIO()
|
||||
@ -376,7 +414,7 @@ def formatargspec(function, args, varargs=None, varkw=None, defaults=None,
|
||||
arg_fd.write(format_arg_with_annotation(arg))
|
||||
if defaults and i >= defaults_start:
|
||||
arg_fd.write(' = ' if arg in annotations else '=')
|
||||
arg_fd.write(object_description(defaults[i - defaults_start]))
|
||||
arg_fd.write(object_description(defaults[i - defaults_start])) # type: ignore
|
||||
formatted.append(arg_fd.getvalue())
|
||||
|
||||
if varargs:
|
||||
@ -391,7 +429,7 @@ def formatargspec(function, args, varargs=None, varkw=None, defaults=None,
|
||||
arg_fd.write(format_arg_with_annotation(kwarg))
|
||||
if kwonlydefaults and kwarg in kwonlydefaults:
|
||||
arg_fd.write(' = ' if kwarg in annotations else '=')
|
||||
arg_fd.write(object_description(kwonlydefaults[kwarg]))
|
||||
arg_fd.write(object_description(kwonlydefaults[kwarg])) # type: ignore
|
||||
formatted.append(arg_fd.getvalue())
|
||||
|
||||
if varkw:
|
||||
@ -438,6 +476,7 @@ class Documenter(object):
|
||||
|
||||
@staticmethod
|
||||
def get_attr(obj, name, *defargs):
|
||||
# type: (Any, unicode, Any) -> Any
|
||||
"""getattr() override for types such as Zope interfaces."""
|
||||
for typ, func in iteritems(AutoDirective._special_attrgetters):
|
||||
if isinstance(obj, typ):
|
||||
@ -446,10 +485,12 @@ class Documenter(object):
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, unicode, bool, Any) -> bool
|
||||
"""Called to see if a member can be documented by this documenter."""
|
||||
raise NotImplementedError('must be implemented in subclasses')
|
||||
|
||||
def __init__(self, directive, name, indent=u''):
|
||||
# type: (Directive, unicode, unicode) -> None
|
||||
self.directive = directive
|
||||
self.env = directive.env
|
||||
self.options = directive.genopt
|
||||
@ -457,27 +498,29 @@ class Documenter(object):
|
||||
self.indent = indent
|
||||
# the module and object path within the module, and the fully
|
||||
# qualified name (all set after resolve_name succeeds)
|
||||
self.modname = None
|
||||
self.module = None
|
||||
self.objpath = None
|
||||
self.fullname = None
|
||||
self.modname = None # type: str
|
||||
self.module = None # type: ModuleType
|
||||
self.objpath = None # type: List[unicode]
|
||||
self.fullname = None # type: unicode
|
||||
# extra signature items (arguments and return annotation,
|
||||
# also set after resolve_name succeeds)
|
||||
self.args = None
|
||||
self.retann = None
|
||||
self.args = None # type: unicode
|
||||
self.retann = None # type: unicode
|
||||
# the object to document (set after import_object succeeds)
|
||||
self.object = None
|
||||
self.object_name = None
|
||||
self.object = None # type: Any
|
||||
self.object_name = None # type: unicode
|
||||
# the parent/owner of the object to document
|
||||
self.parent = None
|
||||
self.parent = None # type: Any
|
||||
# the module analyzer to get at attribute docs, or None
|
||||
self.analyzer = None
|
||||
self.analyzer = None # type: Any
|
||||
|
||||
def add_line(self, line, source, *lineno):
|
||||
# type: (unicode, unicode, int) -> None
|
||||
"""Append one line of generated reST to the output."""
|
||||
self.directive.result.append(self.indent + line, source, *lineno)
|
||||
|
||||
def resolve_name(self, modname, parents, path, base):
|
||||
# type: (str, Any, str, Any) -> Tuple[str, List[unicode]]
|
||||
"""Resolve the module and name of the object to document given by the
|
||||
arguments and the current module/class.
|
||||
|
||||
@ -488,6 +531,7 @@ class Documenter(object):
|
||||
raise NotImplementedError('must be implemented in subclasses')
|
||||
|
||||
def parse_name(self):
|
||||
# type: () -> bool
|
||||
"""Determine what module to import and what attribute to document.
|
||||
|
||||
Returns True and sets *self.modname*, *self.objpath*, *self.fullname*,
|
||||
@ -498,7 +542,7 @@ class Documenter(object):
|
||||
# an autogenerated one
|
||||
try:
|
||||
explicit_modname, path, base, args, retann = \
|
||||
py_ext_sig_re.match(self.name).groups()
|
||||
py_ext_sig_re.match(self.name).groups() # type: ignore
|
||||
except AttributeError:
|
||||
self.directive.warn('invalid signature for auto%s (%r)' %
|
||||
(self.objtype, self.name))
|
||||
@ -512,8 +556,7 @@ class Documenter(object):
|
||||
modname = None
|
||||
parents = []
|
||||
|
||||
self.modname, self.objpath = \
|
||||
self.resolve_name(modname, parents, path, base)
|
||||
self.modname, self.objpath = self.resolve_name(modname, parents, path, base)
|
||||
|
||||
if not self.modname:
|
||||
return False
|
||||
@ -525,6 +568,7 @@ class Documenter(object):
|
||||
return True
|
||||
|
||||
def import_object(self):
|
||||
# type: () -> bool
|
||||
"""Import the object given by *self.modname* and *self.objpath* and set
|
||||
it as *self.object*.
|
||||
|
||||
@ -568,13 +612,14 @@ class Documenter(object):
|
||||
errmsg += '; the following exception was raised:\n%s' % \
|
||||
traceback.format_exc()
|
||||
if PY2:
|
||||
errmsg = errmsg.decode('utf-8')
|
||||
errmsg = errmsg.decode('utf-8') # type: ignore
|
||||
dbg(errmsg)
|
||||
self.directive.warn(errmsg)
|
||||
self.env.note_reread()
|
||||
return False
|
||||
|
||||
def get_real_modname(self):
|
||||
# type: () -> str
|
||||
"""Get the real module name of an object to document.
|
||||
|
||||
It can differ from the name of the module through which the object was
|
||||
@ -583,6 +628,7 @@ class Documenter(object):
|
||||
return self.get_attr(self.object, '__module__', None) or self.modname
|
||||
|
||||
def check_module(self):
|
||||
# type: () -> bool
|
||||
"""Check if *self.object* is really defined in the module given by
|
||||
*self.modname*.
|
||||
"""
|
||||
@ -595,6 +641,7 @@ class Documenter(object):
|
||||
return True
|
||||
|
||||
def format_args(self):
|
||||
# type: () -> unicode
|
||||
"""Format the argument signature of *self.object*.
|
||||
|
||||
Should return None if the object does not have a signature.
|
||||
@ -602,6 +649,7 @@ class Documenter(object):
|
||||
return None
|
||||
|
||||
def format_name(self):
|
||||
# type: () -> unicode
|
||||
"""Format the name of *self.object*.
|
||||
|
||||
This normally should be something that can be parsed by the generated
|
||||
@ -613,13 +661,14 @@ class Documenter(object):
|
||||
return '.'.join(self.objpath) or self.modname
|
||||
|
||||
def format_signature(self):
|
||||
# type: () -> unicode
|
||||
"""Format the signature (arguments and return annotation) of the object.
|
||||
|
||||
Let the user process it via the ``autodoc-process-signature`` event.
|
||||
"""
|
||||
if self.args is not None:
|
||||
# signature given explicitly
|
||||
args = "(%s)" % self.args
|
||||
args = "(%s)" % self.args # type: unicode
|
||||
else:
|
||||
# try to introspect the signature
|
||||
try:
|
||||
@ -643,6 +692,7 @@ class Documenter(object):
|
||||
return ''
|
||||
|
||||
def add_directive_header(self, sig):
|
||||
# type: (unicode) -> None
|
||||
"""Add the directive header and options to the generated content."""
|
||||
domain = getattr(self, 'domain', 'py')
|
||||
directive = getattr(self, 'directivetype', self.objtype)
|
||||
@ -658,6 +708,7 @@ class Documenter(object):
|
||||
self.add_line(u' :module: %s' % self.modname, sourcename)
|
||||
|
||||
def get_doc(self, encoding=None, ignore=1):
|
||||
# type: (unicode, int) -> List[List[unicode]]
|
||||
"""Decode and return lines of the docstring(s) for the object."""
|
||||
docstring = self.get_attr(self.object, '__doc__', None)
|
||||
# make sure we have Unicode docstrings, then sanitize and split
|
||||
@ -671,6 +722,7 @@ class Documenter(object):
|
||||
return []
|
||||
|
||||
def process_doc(self, docstrings):
|
||||
# type: (List[List[unicode]]) -> Iterator[unicode]
|
||||
"""Let the user process the docstrings before adding them."""
|
||||
for docstringlines in docstrings:
|
||||
if self.env.app:
|
||||
@ -682,6 +734,7 @@ class Documenter(object):
|
||||
yield line
|
||||
|
||||
def get_sourcename(self):
|
||||
# type: () -> unicode
|
||||
if self.analyzer:
|
||||
# prevent encoding errors when the file name is non-ASCII
|
||||
if not isinstance(self.analyzer.srcname, text_type):
|
||||
@ -693,6 +746,7 @@ class Documenter(object):
|
||||
return u'docstring of %s' % self.fullname
|
||||
|
||||
def add_content(self, more_content, no_docstring=False):
|
||||
# type: (Any, bool) -> None
|
||||
"""Add content from docstrings, attribute documentation and user."""
|
||||
# set sourcename and add content from attribute documentation
|
||||
sourcename = self.get_sourcename()
|
||||
@ -724,6 +778,7 @@ class Documenter(object):
|
||||
self.add_line(line, src[0], src[1])
|
||||
|
||||
def get_object_members(self, want_all):
|
||||
# type: (bool) -> Tuple[bool, List[Tuple[unicode, object]]]
|
||||
"""Return `(members_check_module, members)` where `members` is a
|
||||
list of `(membername, member)` pairs of the members of *self.object*.
|
||||
|
||||
@ -775,6 +830,7 @@ class Documenter(object):
|
||||
return False, sorted(members)
|
||||
|
||||
def filter_members(self, members, want_all):
|
||||
# type: (List[Tuple[unicode, Any]], bool) -> List[Tuple[unicode, Any, bool]]
|
||||
"""Filter the given member list.
|
||||
|
||||
Members are skipped if
|
||||
@ -852,6 +908,7 @@ class Documenter(object):
|
||||
return ret
|
||||
|
||||
def document_members(self, all_members=False):
|
||||
# type: (bool) -> None
|
||||
"""Generate reST for member documentation.
|
||||
|
||||
If *all_members* is True, do all members, else those given by
|
||||
@ -873,7 +930,7 @@ class Documenter(object):
|
||||
if membername not in self.options.exclude_members]
|
||||
|
||||
# document non-skipped members
|
||||
memberdocumenters = []
|
||||
memberdocumenters = [] # type: List[Tuple[Documenter, bool]]
|
||||
for (mname, member, isattr) in self.filter_members(members, want_all):
|
||||
classes = [cls for cls in itervalues(AutoDirective._registry)
|
||||
if cls.can_document_member(member, mname, isattr, self)]
|
||||
@ -914,6 +971,7 @@ class Documenter(object):
|
||||
|
||||
def generate(self, more_content=None, real_modname=None,
|
||||
check_module=False, all_members=False):
|
||||
# type: (Any, str, bool, bool) -> None
|
||||
"""Generate reST for the object given by *self.name*, and possibly for
|
||||
its members.
|
||||
|
||||
@ -1007,15 +1065,18 @@ class ModuleDocumenter(Documenter):
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, unicode, bool, Any) -> bool
|
||||
# don't document submodules automatically
|
||||
return False
|
||||
|
||||
def resolve_name(self, modname, parents, path, base):
|
||||
# type: (str, Any, str, Any) -> Tuple[str, List[unicode]]
|
||||
if modname is not None:
|
||||
self.directive.warn('"::" in automodule name doesn\'t make sense')
|
||||
return (path or '') + base, []
|
||||
|
||||
def parse_name(self):
|
||||
# type: () -> bool
|
||||
ret = Documenter.parse_name(self)
|
||||
if self.args or self.retann:
|
||||
self.directive.warn('signature arguments or return annotation '
|
||||
@ -1023,6 +1084,7 @@ class ModuleDocumenter(Documenter):
|
||||
return ret
|
||||
|
||||
def add_directive_header(self, sig):
|
||||
# type: (unicode) -> None
|
||||
Documenter.add_directive_header(self, sig)
|
||||
|
||||
sourcename = self.get_sourcename()
|
||||
@ -1038,6 +1100,7 @@ class ModuleDocumenter(Documenter):
|
||||
self.add_line(u' :deprecated:', sourcename)
|
||||
|
||||
def get_object_members(self, want_all):
|
||||
# type: (bool) -> Tuple[bool, List[Tuple[unicode, object]]]
|
||||
if want_all:
|
||||
if not hasattr(self.object, '__all__'):
|
||||
# for implicit module members, check __module__ to avoid
|
||||
@ -1074,6 +1137,7 @@ class ModuleLevelDocumenter(Documenter):
|
||||
classes, data/constants).
|
||||
"""
|
||||
def resolve_name(self, modname, parents, path, base):
|
||||
# type: (str, Any, str, Any) -> Tuple[str, List[unicode]]
|
||||
if modname is None:
|
||||
if path:
|
||||
modname = path.rstrip('.')
|
||||
@ -1094,6 +1158,7 @@ class ClassLevelDocumenter(Documenter):
|
||||
attributes).
|
||||
"""
|
||||
def resolve_name(self, modname, parents, path, base):
|
||||
# type: (str, Any, str, Any) -> Tuple[str, List[unicode]]
|
||||
if modname is None:
|
||||
if path:
|
||||
mod_cls = path.rstrip('.')
|
||||
@ -1127,6 +1192,7 @@ class DocstringSignatureMixin(object):
|
||||
"""
|
||||
|
||||
def _find_signature(self, encoding=None):
|
||||
# type: (unicode) -> Tuple[str, str]
|
||||
docstrings = self.get_doc(encoding)
|
||||
self._new_docstrings = docstrings[:]
|
||||
result = None
|
||||
@ -1135,12 +1201,12 @@ class DocstringSignatureMixin(object):
|
||||
if not doclines:
|
||||
continue
|
||||
# match first line of docstring against signature RE
|
||||
match = py_ext_sig_re.match(doclines[0])
|
||||
match = py_ext_sig_re.match(doclines[0]) # type: ignore
|
||||
if not match:
|
||||
continue
|
||||
exmod, path, base, args, retann = match.groups()
|
||||
# the base name must match ours
|
||||
valid_names = [self.objpath[-1]]
|
||||
valid_names = [self.objpath[-1]] # type: ignore
|
||||
if isinstance(self, ClassDocumenter):
|
||||
valid_names.append('__init__')
|
||||
if hasattr(self.object, '__mro__'):
|
||||
@ -1155,19 +1221,21 @@ class DocstringSignatureMixin(object):
|
||||
return result
|
||||
|
||||
def get_doc(self, encoding=None, ignore=1):
|
||||
# type: (unicode, int) -> List[List[unicode]]
|
||||
lines = getattr(self, '_new_docstrings', None)
|
||||
if lines is not None:
|
||||
return lines
|
||||
return Documenter.get_doc(self, encoding, ignore)
|
||||
return Documenter.get_doc(self, encoding, ignore) # type: ignore
|
||||
|
||||
def format_signature(self):
|
||||
if self.args is None and self.env.config.autodoc_docstring_signature:
|
||||
# type: () -> unicode
|
||||
if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore
|
||||
# only act if a signature is not explicitly given already, and if
|
||||
# the feature is enabled
|
||||
result = self._find_signature()
|
||||
if result is not None:
|
||||
self.args, self.retann = result
|
||||
return Documenter.format_signature(self)
|
||||
return Documenter.format_signature(self) # type: ignore
|
||||
|
||||
|
||||
class DocstringStripSignatureMixin(DocstringSignatureMixin):
|
||||
@ -1176,7 +1244,8 @@ class DocstringStripSignatureMixin(DocstringSignatureMixin):
|
||||
feature of stripping any function signature from the docstring.
|
||||
"""
|
||||
def format_signature(self):
|
||||
if self.args is None and self.env.config.autodoc_docstring_signature:
|
||||
# type: () -> unicode
|
||||
if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore
|
||||
# only act if a signature is not explicitly given already, and if
|
||||
# the feature is enabled
|
||||
result = self._find_signature()
|
||||
@ -1185,10 +1254,10 @@ class DocstringStripSignatureMixin(DocstringSignatureMixin):
|
||||
# DocstringSignatureMixin.format_signature.
|
||||
# Documenter.format_signature use self.args value to format.
|
||||
_args, self.retann = result
|
||||
return Documenter.format_signature(self)
|
||||
return Documenter.format_signature(self) # type: ignore
|
||||
|
||||
|
||||
class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
|
||||
class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore
|
||||
"""
|
||||
Specialized Documenter subclass for functions.
|
||||
"""
|
||||
@ -1197,9 +1266,11 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, unicode, bool, Any) -> bool
|
||||
return isinstance(member, (FunctionType, BuiltinFunctionType))
|
||||
|
||||
def format_args(self):
|
||||
# type: () -> unicode
|
||||
if inspect.isbuiltin(self.object) or \
|
||||
inspect.ismethoddescriptor(self.object):
|
||||
# cannot introspect arguments of a C function or method
|
||||
@ -1226,10 +1297,11 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
|
||||
return args
|
||||
|
||||
def document_members(self, all_members=False):
|
||||
# type: (bool) -> None
|
||||
pass
|
||||
|
||||
|
||||
class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
|
||||
class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore
|
||||
"""
|
||||
Specialized Documenter subclass for classes.
|
||||
"""
|
||||
@ -1245,9 +1317,11 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, unicode, bool, Any) -> bool
|
||||
return isinstance(member, class_types)
|
||||
|
||||
def import_object(self):
|
||||
# type: () -> Any
|
||||
ret = ModuleLevelDocumenter.import_object(self)
|
||||
# if the class is documented under another name, document it
|
||||
# as data/attribute
|
||||
@ -1259,6 +1333,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
|
||||
return ret
|
||||
|
||||
def format_args(self):
|
||||
# type: () -> unicode
|
||||
# for classes, the relevant signature is the __init__ method's
|
||||
initmeth = self.get_attr(self.object, '__init__', None)
|
||||
# classes without __init__ method, default __init__ or
|
||||
@ -1278,12 +1353,14 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
|
||||
return formatargspec(initmeth, *argspec)
|
||||
|
||||
def format_signature(self):
|
||||
# type: () -> unicode
|
||||
if self.doc_as_attr:
|
||||
return ''
|
||||
|
||||
return DocstringSignatureMixin.format_signature(self)
|
||||
|
||||
def add_directive_header(self, sig):
|
||||
# type: (unicode) -> None
|
||||
if self.doc_as_attr:
|
||||
self.directivetype = 'attribute'
|
||||
Documenter.add_directive_header(self, sig)
|
||||
@ -1301,6 +1378,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
|
||||
sourcename)
|
||||
|
||||
def get_doc(self, encoding=None, ignore=1):
|
||||
# type: (unicode, int) -> List[List[unicode]]
|
||||
lines = getattr(self, '_new_docstrings', None)
|
||||
if lines is not None:
|
||||
return lines
|
||||
@ -1346,6 +1424,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
|
||||
return doc
|
||||
|
||||
def add_content(self, more_content, no_docstring=False):
|
||||
# type: (Any, bool) -> None
|
||||
if self.doc_as_attr:
|
||||
classname = safe_getattr(self.object, '__name__', None)
|
||||
if classname:
|
||||
@ -1357,6 +1436,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter):
|
||||
ModuleLevelDocumenter.add_content(self, more_content)
|
||||
|
||||
def document_members(self, all_members=False):
|
||||
# type: (bool) -> None
|
||||
if self.doc_as_attr:
|
||||
return
|
||||
ModuleLevelDocumenter.document_members(self, all_members)
|
||||
@ -1374,8 +1454,9 @@ class ExceptionDocumenter(ClassDocumenter):
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, unicode, bool, Any) -> bool
|
||||
return isinstance(member, class_types) and \
|
||||
issubclass(member, BaseException)
|
||||
issubclass(member, BaseException) # type: ignore
|
||||
|
||||
|
||||
class DataDocumenter(ModuleLevelDocumenter):
|
||||
@ -1390,9 +1471,11 @@ class DataDocumenter(ModuleLevelDocumenter):
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, unicode, bool, Any) -> bool
|
||||
return isinstance(parent, ModuleDocumenter) and isattr
|
||||
|
||||
def add_directive_header(self, sig):
|
||||
# type: (unicode) -> None
|
||||
ModuleLevelDocumenter.add_directive_header(self, sig)
|
||||
sourcename = self.get_sourcename()
|
||||
if not self.options.annotation:
|
||||
@ -1409,10 +1492,11 @@ class DataDocumenter(ModuleLevelDocumenter):
|
||||
sourcename)
|
||||
|
||||
def document_members(self, all_members=False):
|
||||
# type: (bool) -> None
|
||||
pass
|
||||
|
||||
|
||||
class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter):
|
||||
class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore
|
||||
"""
|
||||
Specialized Documenter subclass for methods (normal, static and class).
|
||||
"""
|
||||
@ -1422,10 +1506,12 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter):
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, unicode, bool, Any) -> bool
|
||||
return inspect.isroutine(member) and \
|
||||
not isinstance(parent, ModuleDocumenter)
|
||||
|
||||
def import_object(self):
|
||||
# type: () -> Any
|
||||
ret = ClassLevelDocumenter.import_object(self)
|
||||
if not ret:
|
||||
return ret
|
||||
@ -1433,11 +1519,11 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter):
|
||||
# to distinguish classmethod/staticmethod
|
||||
obj = self.parent.__dict__.get(self.object_name)
|
||||
|
||||
if isinstance(obj, classmethod):
|
||||
if isinstance(obj, classmethod): # type: ignore
|
||||
self.directivetype = 'classmethod'
|
||||
# document class and static members before ordinary ones
|
||||
self.member_order = self.member_order - 1
|
||||
elif isinstance(obj, staticmethod):
|
||||
elif isinstance(obj, staticmethod): # type: ignore
|
||||
self.directivetype = 'staticmethod'
|
||||
# document class and static members before ordinary ones
|
||||
self.member_order = self.member_order - 1
|
||||
@ -1446,6 +1532,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter):
|
||||
return ret
|
||||
|
||||
def format_args(self):
|
||||
# type: () -> unicode
|
||||
if inspect.isbuiltin(self.object) or \
|
||||
inspect.ismethoddescriptor(self.object):
|
||||
# can never get arguments of a C function or method
|
||||
@ -1459,10 +1546,11 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter):
|
||||
return args
|
||||
|
||||
def document_members(self, all_members=False):
|
||||
# type: (bool) -> None
|
||||
pass
|
||||
|
||||
|
||||
class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore
|
||||
"""
|
||||
Specialized Documenter subclass for attributes.
|
||||
"""
|
||||
@ -1479,6 +1567,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, unicode, bool, Any) -> bool
|
||||
isdatadesc = isdescriptor(member) and not \
|
||||
isinstance(member, cls.method_types) and not \
|
||||
type(member).__name__ in ("type", "method_descriptor",
|
||||
@ -1488,9 +1577,11 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
not isinstance(member, class_types))
|
||||
|
||||
def document_members(self, all_members=False):
|
||||
# type: (bool) -> None
|
||||
pass
|
||||
|
||||
def import_object(self):
|
||||
# type: () -> Any
|
||||
ret = ClassLevelDocumenter.import_object(self)
|
||||
if isenumattribute(self.object):
|
||||
self.object = self.object.value
|
||||
@ -1503,10 +1594,12 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
return ret
|
||||
|
||||
def get_real_modname(self):
|
||||
# type: () -> str
|
||||
return self.get_attr(self.parent or self.object, '__module__', None) \
|
||||
or self.modname
|
||||
|
||||
def add_directive_header(self, sig):
|
||||
# type: (unicode) -> None
|
||||
ClassLevelDocumenter.add_directive_header(self, sig)
|
||||
sourcename = self.get_sourcename()
|
||||
if not self.options.annotation:
|
||||
@ -1524,6 +1617,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
sourcename)
|
||||
|
||||
def add_content(self, more_content, no_docstring=False):
|
||||
# type: (Any, bool) -> None
|
||||
if not self._datadescriptor:
|
||||
# if it's not a data descriptor, its docstring is very probably the
|
||||
# wrong thing to display
|
||||
@ -1545,10 +1639,12 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
# type: (Any, unicode, bool, Any) -> bool
|
||||
"""This documents only INSTANCEATTR members."""
|
||||
return isattr and (member is INSTANCEATTR)
|
||||
|
||||
def import_object(self):
|
||||
# type: () -> bool
|
||||
"""Never import anything."""
|
||||
# disguise as an attribute
|
||||
self.objtype = 'attribute'
|
||||
@ -1556,6 +1652,7 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
|
||||
return True
|
||||
|
||||
def add_content(self, more_content, no_docstring=False):
|
||||
# type: (Any, bool) -> None
|
||||
"""Never try to get a docstring from the object."""
|
||||
AttributeDocumenter.add_content(self, more_content, no_docstring=True)
|
||||
|
||||
@ -1576,10 +1673,10 @@ class AutoDirective(Directive):
|
||||
attributes of the parents.
|
||||
"""
|
||||
# a registry of objtype -> documenter class
|
||||
_registry = {}
|
||||
_registry = {} # type: Dict[unicode, Type[Documenter]]
|
||||
|
||||
# a registry of type -> getattr function
|
||||
_special_attrgetters = {}
|
||||
_special_attrgetters = {} # type: Dict[Type, Callable]
|
||||
|
||||
# flags that can be given in autodoc_default_flags
|
||||
_default_flags = set([
|
||||
@ -1597,13 +1694,16 @@ class AutoDirective(Directive):
|
||||
option_spec = DefDict(identity)
|
||||
|
||||
def warn(self, msg):
|
||||
# type: (unicode) -> None
|
||||
self.warnings.append(self.reporter.warning(msg, line=self.lineno))
|
||||
|
||||
def run(self):
|
||||
self.filename_set = set() # a set of dependent filenames
|
||||
# type: () -> List[nodes.Node]
|
||||
self.filename_set = set() # type: Set[unicode]
|
||||
# a set of dependent filenames
|
||||
self.reporter = self.state.document.reporter
|
||||
self.env = self.state.document.settings.env
|
||||
self.warnings = []
|
||||
self.warnings = [] # type: List[unicode]
|
||||
self.result = ViewList()
|
||||
|
||||
try:
|
||||
@ -1667,6 +1767,7 @@ class AutoDirective(Directive):
|
||||
|
||||
|
||||
def add_documenter(cls):
|
||||
# type: (Type[Documenter]) -> None
|
||||
"""Register a new Documenter."""
|
||||
if not issubclass(cls, Documenter):
|
||||
raise ExtensionError('autodoc documenter %r must be a subclass '
|
||||
@ -1679,6 +1780,7 @@ def add_documenter(cls):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.add_autodocumenter(ModuleDocumenter)
|
||||
app.add_autodocumenter(ClassDocumenter)
|
||||
app.add_autodocumenter(ExceptionDocumenter)
|
||||
|
@ -62,6 +62,7 @@ from six import string_types
|
||||
from types import ModuleType
|
||||
|
||||
from six import text_type
|
||||
|
||||
from docutils.parsers.rst import directives
|
||||
from docutils.statemachine import ViewList
|
||||
from docutils import nodes
|
||||
@ -73,6 +74,14 @@ from sphinx.util.compat import Directive
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.ext.autodoc import Options
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Tuple, Type, Union # NOQA
|
||||
from docutils.utils import Inliner # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
from sphinx.ext.autodoc import Documenter # NOQA
|
||||
|
||||
|
||||
# -- autosummary_toc node ------------------------------------------------------
|
||||
|
||||
@ -81,6 +90,7 @@ class autosummary_toc(nodes.comment):
|
||||
|
||||
|
||||
def process_autosummary_toc(app, doctree):
|
||||
# type: (Sphinx, nodes.Node) -> None
|
||||
"""Insert items described in autosummary:: to the TOC tree, but do
|
||||
not generate the toctree:: list.
|
||||
"""
|
||||
@ -105,11 +115,13 @@ def process_autosummary_toc(app, doctree):
|
||||
|
||||
|
||||
def autosummary_toc_visit_html(self, node):
|
||||
# type: (nodes.NodeVisitor, autosummary_toc) -> None
|
||||
"""Hide autosummary toctree list in HTML output."""
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def autosummary_noop(self, node):
|
||||
# type: (nodes.NodeVisitor, nodes.Node) -> None
|
||||
pass
|
||||
|
||||
|
||||
@ -120,6 +132,7 @@ class autosummary_table(nodes.comment):
|
||||
|
||||
|
||||
def autosummary_table_visit_html(self, node):
|
||||
# type: (nodes.NodeVisitor, autosummary_table) -> None
|
||||
"""Make the first column of the table non-breaking."""
|
||||
try:
|
||||
tbody = node[0][0][-1]
|
||||
@ -138,11 +151,12 @@ def autosummary_table_visit_html(self, node):
|
||||
# -- autodoc integration -------------------------------------------------------
|
||||
|
||||
class FakeDirective(object):
|
||||
env = {}
|
||||
env = {} # type: Dict
|
||||
genopt = Options()
|
||||
|
||||
|
||||
def get_documenter(obj, parent):
|
||||
# type: (Any, Any) -> Type[Documenter]
|
||||
"""Get an autodoc.Documenter class suitable for documenting the given
|
||||
object.
|
||||
|
||||
@ -198,13 +212,15 @@ class Autosummary(Directive):
|
||||
}
|
||||
|
||||
def warn(self, msg):
|
||||
# type: (unicode) -> None
|
||||
self.warnings.append(self.state.document.reporter.warning(
|
||||
msg, line=self.lineno))
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
self.env = env = self.state.document.settings.env
|
||||
self.genopt = Options()
|
||||
self.warnings = []
|
||||
self.warnings = [] # type: List[nodes.Node]
|
||||
self.result = ViewList()
|
||||
|
||||
names = [x.strip().split()[0] for x in self.content
|
||||
@ -237,6 +253,7 @@ class Autosummary(Directive):
|
||||
return self.warnings + nodes
|
||||
|
||||
def get_items(self, names):
|
||||
# type: (List[unicode]) -> List[Tuple[unicode, unicode, unicode, unicode]]
|
||||
"""Try to import the given names, and return a list of
|
||||
``[(name, signature, summary_string, real_name), ...]``.
|
||||
"""
|
||||
@ -244,7 +261,7 @@ class Autosummary(Directive):
|
||||
|
||||
prefixes = get_import_prefixes_from_env(env)
|
||||
|
||||
items = []
|
||||
items = [] # type: List[Tuple[unicode, unicode, unicode, unicode]]
|
||||
|
||||
max_item_chars = 50
|
||||
|
||||
@ -334,6 +351,7 @@ class Autosummary(Directive):
|
||||
return items
|
||||
|
||||
def get_table(self, items):
|
||||
# type: (List[Tuple[unicode, unicode, unicode, unicode]]) -> List[Union[addnodes.tabular_col_spec, autosummary_table]] # NOQA
|
||||
"""Generate a proper list of table nodes for autosummary:: directive.
|
||||
|
||||
*items* is a list produced by :meth:`get_items`.
|
||||
@ -352,6 +370,7 @@ class Autosummary(Directive):
|
||||
group.append(body)
|
||||
|
||||
def append_row(*column_texts):
|
||||
# type: (unicode) -> None
|
||||
row = nodes.row('')
|
||||
for text in column_texts:
|
||||
node = nodes.paragraph('')
|
||||
@ -369,7 +388,7 @@ class Autosummary(Directive):
|
||||
for name, sig, summary, real_name in items:
|
||||
qualifier = 'obj'
|
||||
if 'nosignatures' not in self.options:
|
||||
col1 = ':%s:`%s <%s>`\ %s' % (qualifier, name, real_name, rst.escape(sig))
|
||||
col1 = ':%s:`%s <%s>`\ %s' % (qualifier, name, real_name, rst.escape(sig)) # type: unicode # NOQA
|
||||
else:
|
||||
col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name)
|
||||
col2 = summary
|
||||
@ -379,6 +398,7 @@ class Autosummary(Directive):
|
||||
|
||||
|
||||
def mangle_signature(sig, max_chars=30):
|
||||
# type: (unicode, int) -> unicode
|
||||
"""Reformat a function signature to a more compact form."""
|
||||
s = re.sub(r"^\((.*)\)$", r"\1", sig).strip()
|
||||
|
||||
@ -388,12 +408,12 @@ def mangle_signature(sig, max_chars=30):
|
||||
s = re.sub(r"'[^']*'", "", s)
|
||||
|
||||
# Parse the signature to arguments + options
|
||||
args = []
|
||||
opts = []
|
||||
args = [] # type: List[unicode]
|
||||
opts = [] # type: List[unicode]
|
||||
|
||||
opt_re = re.compile(r"^(.*, |)([a-zA-Z0-9_*]+)=")
|
||||
while s:
|
||||
m = opt_re.search(s)
|
||||
m = opt_re.search(s) # type: ignore
|
||||
if not m:
|
||||
# The rest are arguments
|
||||
args = s.split(', ')
|
||||
@ -415,6 +435,7 @@ def mangle_signature(sig, max_chars=30):
|
||||
|
||||
|
||||
def limited_join(sep, items, max_chars=30, overflow_marker="..."):
|
||||
# type: (unicode, List[unicode], int, unicode) -> unicode
|
||||
"""Join a number of strings to one, limiting the length to *max_chars*.
|
||||
|
||||
If the string overflows this limit, replace the last fitting item by
|
||||
@ -441,11 +462,12 @@ def limited_join(sep, items, max_chars=30, overflow_marker="..."):
|
||||
# -- Importing items -----------------------------------------------------------
|
||||
|
||||
def get_import_prefixes_from_env(env):
|
||||
# type: (BuildEnvironment) -> List
|
||||
"""
|
||||
Obtain current Python import prefixes (for `import_by_name`)
|
||||
from ``document.env``
|
||||
"""
|
||||
prefixes = [None]
|
||||
prefixes = [None] # type: List
|
||||
|
||||
currmodule = env.ref_context.get('py:module')
|
||||
if currmodule:
|
||||
@ -462,6 +484,7 @@ def get_import_prefixes_from_env(env):
|
||||
|
||||
|
||||
def import_by_name(name, prefixes=[None]):
|
||||
# type: (unicode, List) -> Tuple[unicode, Any, Any, unicode]
|
||||
"""Import a Python object that has the given *name*, under one of the
|
||||
*prefixes*. The first name that succeeds is used.
|
||||
"""
|
||||
@ -480,6 +503,7 @@ def import_by_name(name, prefixes=[None]):
|
||||
|
||||
|
||||
def _import_by_name(name):
|
||||
# type: (str) -> Tuple[Any, Any, unicode]
|
||||
"""Import a Python object given its full name."""
|
||||
try:
|
||||
name_parts = name.split('.')
|
||||
@ -524,6 +548,7 @@ def _import_by_name(name):
|
||||
|
||||
def autolink_role(typ, rawtext, etext, lineno, inliner,
|
||||
options={}, content=[]):
|
||||
# type: (unicode, unicode, unicode, int, Inliner, Dict, List[unicode]) -> Tuple[List[nodes.Node], List[nodes.Node]] # NOQA
|
||||
"""Smart linking role.
|
||||
|
||||
Expands to ':obj:`text`' if `text` is an object that can be imported;
|
||||
@ -539,21 +564,24 @@ def autolink_role(typ, rawtext, etext, lineno, inliner,
|
||||
name, obj, parent, modname = import_by_name(pnode['reftarget'], prefixes)
|
||||
except ImportError:
|
||||
content = pnode[0]
|
||||
r[0][0] = nodes.emphasis(rawtext, content[0].astext(),
|
||||
classes=content['classes'])
|
||||
r[0][0] = nodes.emphasis(rawtext, content[0].astext(), # type: ignore
|
||||
classes=content['classes']) # type: ignore
|
||||
return r
|
||||
|
||||
|
||||
def get_rst_suffix(app):
|
||||
# type: (Sphinx) -> unicode
|
||||
def get_supported_format(suffix):
|
||||
# type: (unicode) -> Tuple[unicode]
|
||||
parser_class = app.config.source_parsers.get(suffix)
|
||||
if parser_class is None:
|
||||
return ('restructuredtext',)
|
||||
if isinstance(parser_class, string_types):
|
||||
parser_class = import_object(parser_class, 'source parser')
|
||||
parser_class = import_object(parser_class, 'source parser') # type: ignore
|
||||
return parser_class.supported
|
||||
|
||||
for suffix in app.config.source_suffix:
|
||||
suffix = None # type: unicode
|
||||
for suffix in app.config.source_suffix: # type: ignore
|
||||
if 'restructuredtext' in get_supported_format(suffix):
|
||||
return suffix
|
||||
|
||||
@ -561,6 +589,7 @@ def get_rst_suffix(app):
|
||||
|
||||
|
||||
def process_generate_options(app):
|
||||
# type: (Sphinx) -> None
|
||||
genfiles = app.config.autosummary_generate
|
||||
|
||||
if genfiles and not hasattr(genfiles, '__len__'):
|
||||
@ -589,6 +618,7 @@ def process_generate_options(app):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
# I need autodoc
|
||||
app.setup_extension('sphinx.ext.autodoc')
|
||||
app.add_node(autosummary_toc,
|
||||
|
@ -49,8 +49,16 @@ add_documenter(MethodDocumenter)
|
||||
add_documenter(AttributeDocumenter)
|
||||
add_documenter(InstanceAttributeDocumenter)
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Tuple # NOQA
|
||||
from sphinx import addnodes # NOQA
|
||||
from sphinx.builders import Builder # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
|
||||
|
||||
def main(argv=sys.argv):
|
||||
# type: (List[str]) -> None
|
||||
usage = """%prog [OPTIONS] SOURCEFILE ..."""
|
||||
p = optparse.OptionParser(usage.strip())
|
||||
p.add_option("-o", "--output-dir", action="store", type="string",
|
||||
@ -73,10 +81,12 @@ def main(argv=sys.argv):
|
||||
|
||||
|
||||
def _simple_info(msg):
|
||||
# type: (unicode) -> None
|
||||
print(msg)
|
||||
|
||||
|
||||
def _simple_warn(msg):
|
||||
# type: (unicode) -> None
|
||||
print('WARNING: ' + msg, file=sys.stderr)
|
||||
|
||||
|
||||
@ -85,6 +95,7 @@ def _simple_warn(msg):
|
||||
def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
warn=_simple_warn, info=_simple_info,
|
||||
base_path=None, builder=None, template_dir=None):
|
||||
# type: (List[unicode], unicode, unicode, Callable, Callable, unicode, Builder, unicode) -> None # NOQA
|
||||
|
||||
showed_sources = list(sorted(sources))
|
||||
if len(showed_sources) > 20:
|
||||
@ -99,6 +110,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
sources = [os.path.join(base_path, filename) for filename in sources]
|
||||
|
||||
# create our own templating environment
|
||||
template_dirs = None # type: List[unicode]
|
||||
template_dirs = [os.path.join(package_dir, 'ext',
|
||||
'autosummary', 'templates')]
|
||||
if builder is not None:
|
||||
@ -154,7 +166,8 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
template = template_env.get_template('autosummary/base.rst')
|
||||
|
||||
def get_members(obj, typ, include_public=[]):
|
||||
items = []
|
||||
# type: (Any, unicode, List[unicode]) -> Tuple[List[unicode], List[unicode]]
|
||||
items = [] # type: List[unicode]
|
||||
for name in dir(obj):
|
||||
try:
|
||||
documenter = get_documenter(safe_getattr(obj, name),
|
||||
@ -167,7 +180,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
if x in include_public or not x.startswith('_')]
|
||||
return public, items
|
||||
|
||||
ns = {}
|
||||
ns = {} # type: Dict[unicode, Any]
|
||||
|
||||
if doc.objtype == 'module':
|
||||
ns['members'] = dir(obj)
|
||||
@ -215,21 +228,23 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
|
||||
# -- Finding documented entries in files ---------------------------------------
|
||||
|
||||
def find_autosummary_in_files(filenames):
|
||||
# type: (List[unicode]) -> List[Tuple[unicode, unicode, unicode]]
|
||||
"""Find out what items are documented in source/*.rst.
|
||||
|
||||
See `find_autosummary_in_lines`.
|
||||
"""
|
||||
documented = []
|
||||
documented = [] # type: List[Tuple[unicode, unicode, unicode]]
|
||||
for filename in filenames:
|
||||
with codecs.open(filename, 'r', encoding='utf-8',
|
||||
with codecs.open(filename, 'r', encoding='utf-8', # type: ignore
|
||||
errors='ignore') as f:
|
||||
lines = f.read().splitlines()
|
||||
documented.extend(find_autosummary_in_lines(lines,
|
||||
documented.extend(find_autosummary_in_lines(lines, # type: ignore
|
||||
filename=filename))
|
||||
return documented
|
||||
|
||||
|
||||
def find_autosummary_in_docstring(name, module=None, filename=None):
|
||||
# type: (unicode, Any, unicode) -> List[Tuple[unicode, unicode, unicode]]
|
||||
"""Find out what items are documented in the given object's docstring.
|
||||
|
||||
See `find_autosummary_in_lines`.
|
||||
@ -249,6 +264,7 @@ def find_autosummary_in_docstring(name, module=None, filename=None):
|
||||
|
||||
|
||||
def find_autosummary_in_lines(lines, module=None, filename=None):
|
||||
# type: (List[unicode], Any, unicode) -> List[Tuple[unicode, unicode, unicode]]
|
||||
"""Find out what items appear in autosummary:: directives in the
|
||||
given lines.
|
||||
|
||||
@ -268,9 +284,9 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
|
||||
toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$')
|
||||
template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$')
|
||||
|
||||
documented = []
|
||||
documented = [] # type: List[Tuple[unicode, unicode, unicode]]
|
||||
|
||||
toctree = None
|
||||
toctree = None # type: unicode
|
||||
template = None
|
||||
current_module = module
|
||||
in_autosummary = False
|
||||
@ -278,7 +294,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
|
||||
|
||||
for line in lines:
|
||||
if in_autosummary:
|
||||
m = toctree_arg_re.match(line)
|
||||
m = toctree_arg_re.match(line) # type: ignore
|
||||
if m:
|
||||
toctree = m.group(1)
|
||||
if filename:
|
||||
@ -286,7 +302,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
|
||||
toctree)
|
||||
continue
|
||||
|
||||
m = template_arg_re.match(line)
|
||||
m = template_arg_re.match(line) # type: ignore
|
||||
if m:
|
||||
template = m.group(1).strip()
|
||||
continue
|
||||
@ -294,7 +310,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
|
||||
if line.strip().startswith(':'):
|
||||
continue # skip options
|
||||
|
||||
m = autosummary_item_re.match(line)
|
||||
m = autosummary_item_re.match(line) # type: ignore
|
||||
if m:
|
||||
name = m.group(1).strip()
|
||||
if name.startswith('~'):
|
||||
@ -310,7 +326,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
|
||||
|
||||
in_autosummary = False
|
||||
|
||||
m = autosummary_re.match(line)
|
||||
m = autosummary_re.match(line) # type: ignore
|
||||
if m:
|
||||
in_autosummary = True
|
||||
base_indent = m.group(1)
|
||||
@ -318,7 +334,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
|
||||
template = None
|
||||
continue
|
||||
|
||||
m = automodule_re.search(line)
|
||||
m = automodule_re.search(line) # type: ignore
|
||||
if m:
|
||||
current_module = m.group(1).strip()
|
||||
# recurse into the automodule docstring
|
||||
@ -326,7 +342,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None):
|
||||
current_module, filename=filename))
|
||||
continue
|
||||
|
||||
m = module_re.match(line)
|
||||
m = module_re.match(line) # type: ignore
|
||||
if m:
|
||||
current_module = m.group(2)
|
||||
continue
|
||||
|
@ -22,14 +22,21 @@ import sphinx
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.util.inspect import safe_getattr
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, IO, Pattern, Tuple # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
|
||||
# utility
|
||||
def write_header(f, text, char='-'):
|
||||
# type:(IO, unicode, unicode) -> None
|
||||
f.write(text + '\n')
|
||||
f.write(char * len(text) + '\n')
|
||||
|
||||
|
||||
def compile_regex_list(name, exps, warnfunc):
|
||||
# type: (unicode, unicode, Callable) -> List[Pattern]
|
||||
lst = []
|
||||
for exp in exps:
|
||||
try:
|
||||
@ -44,19 +51,20 @@ class CoverageBuilder(Builder):
|
||||
name = 'coverage'
|
||||
|
||||
def init(self):
|
||||
self.c_sourcefiles = []
|
||||
# type: () -> None
|
||||
self.c_sourcefiles = [] # type: List[unicode]
|
||||
for pattern in self.config.coverage_c_path:
|
||||
pattern = path.join(self.srcdir, pattern)
|
||||
self.c_sourcefiles.extend(glob.glob(pattern))
|
||||
|
||||
self.c_regexes = []
|
||||
self.c_regexes = [] # type: List[Tuple[unicode, Pattern]]
|
||||
for (name, exp) in self.config.coverage_c_regexes.items():
|
||||
try:
|
||||
self.c_regexes.append((name, re.compile(exp)))
|
||||
except Exception:
|
||||
self.warn('invalid regex %r in coverage_c_regexes' % exp)
|
||||
|
||||
self.c_ignorexps = {}
|
||||
self.c_ignorexps = {} # type: Dict[unicode, List[Pattern]]
|
||||
for (name, exps) in iteritems(self.config.coverage_ignore_c_items):
|
||||
self.c_ignorexps[name] = compile_regex_list(
|
||||
'coverage_ignore_c_items', exps, self.warn)
|
||||
@ -71,18 +79,21 @@ class CoverageBuilder(Builder):
|
||||
self.warn)
|
||||
|
||||
def get_outdated_docs(self):
|
||||
# type: () -> unicode
|
||||
return 'coverage overview'
|
||||
|
||||
def write(self, *ignored):
|
||||
self.py_undoc = {}
|
||||
# type: (Any) -> None
|
||||
self.py_undoc = {} # type: Dict[unicode, Dict[unicode, Any]]
|
||||
self.build_py_coverage()
|
||||
self.write_py_coverage()
|
||||
|
||||
self.c_undoc = {}
|
||||
self.c_undoc = {} # type: Dict[unicode, Set[Tuple[unicode, unicode]]]
|
||||
self.build_c_coverage()
|
||||
self.write_c_coverage()
|
||||
|
||||
def build_c_coverage(self):
|
||||
# type: () -> None
|
||||
# Fetch all the info from the header files
|
||||
c_objects = self.env.domaindata['c']['objects']
|
||||
for filename in self.c_sourcefiles:
|
||||
@ -104,6 +115,7 @@ class CoverageBuilder(Builder):
|
||||
self.c_undoc[filename] = undoc
|
||||
|
||||
def write_c_coverage(self):
|
||||
# type: () -> None
|
||||
output_file = path.join(self.outdir, 'c.txt')
|
||||
with open(output_file, 'w') as op:
|
||||
if self.config.coverage_write_headline:
|
||||
@ -117,6 +129,7 @@ class CoverageBuilder(Builder):
|
||||
op.write('\n')
|
||||
|
||||
def build_py_coverage(self):
|
||||
# type: () -> None
|
||||
objects = self.env.domaindata['py']['objects']
|
||||
modules = self.env.domaindata['py']['modules']
|
||||
|
||||
@ -140,7 +153,7 @@ class CoverageBuilder(Builder):
|
||||
continue
|
||||
|
||||
funcs = []
|
||||
classes = {}
|
||||
classes = {} # type: Dict[unicode, List[unicode]]
|
||||
|
||||
for name, obj in inspect.getmembers(mod):
|
||||
# diverse module attributes are ignored:
|
||||
@ -177,7 +190,7 @@ class CoverageBuilder(Builder):
|
||||
classes[name] = []
|
||||
continue
|
||||
|
||||
attrs = []
|
||||
attrs = [] # type: List[unicode]
|
||||
|
||||
for attr_name in dir(obj):
|
||||
if attr_name not in obj.__dict__:
|
||||
@ -207,6 +220,7 @@ class CoverageBuilder(Builder):
|
||||
self.py_undoc[mod_name] = {'funcs': funcs, 'classes': classes}
|
||||
|
||||
def write_py_coverage(self):
|
||||
# type: () -> None
|
||||
output_file = path.join(self.outdir, 'python.txt')
|
||||
failed = []
|
||||
with open(output_file, 'w') as op:
|
||||
@ -242,6 +256,7 @@ class CoverageBuilder(Builder):
|
||||
op.writelines(' * %s -- %s\n' % x for x in failed)
|
||||
|
||||
def finish(self):
|
||||
# type: () -> None
|
||||
# dump the coverage data to a pickle file too
|
||||
picklepath = path.join(self.outdir, 'undoc.pickle')
|
||||
with open(picklepath, 'wb') as dumpfile:
|
||||
@ -249,6 +264,7 @@ class CoverageBuilder(Builder):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.add_builder(CoverageBuilder)
|
||||
app.add_config_value('coverage_ignore_modules', [], False)
|
||||
app.add_config_value('coverage_ignore_functions', [], False)
|
||||
|
@ -19,6 +19,7 @@ from os import path
|
||||
import doctest
|
||||
|
||||
from six import itervalues, StringIO, binary_type, text_type, PY2
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives
|
||||
|
||||
@ -27,14 +28,20 @@ from sphinx.builders import Builder
|
||||
from sphinx.util import force_decode
|
||||
from sphinx.util.nodes import set_source_info
|
||||
from sphinx.util.compat import Directive
|
||||
from sphinx.util.console import bold
|
||||
from sphinx.util.console import bold # type: ignore
|
||||
from sphinx.util.osutil import fs_encoding
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, IO, Iterable, Sequence, Tuple # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
blankline_re = re.compile(r'^\s*<BLANKLINE>', re.MULTILINE)
|
||||
doctestopt_re = re.compile(r'#\s*doctest:.+$', re.MULTILINE)
|
||||
|
||||
if PY2:
|
||||
def doctest_encode(text, encoding):
|
||||
# type: (str, unicode) -> unicode
|
||||
if isinstance(text, text_type):
|
||||
text = text.encode(encoding)
|
||||
if text.startswith(codecs.BOM_UTF8):
|
||||
@ -42,6 +49,7 @@ if PY2:
|
||||
return text
|
||||
else:
|
||||
def doctest_encode(text, encoding):
|
||||
# type: (unicode, unicode) -> unicode
|
||||
return text
|
||||
|
||||
|
||||
@ -58,6 +66,7 @@ class TestDirective(Directive):
|
||||
final_argument_whitespace = True
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
# use ordinary docutils nodes for test code: they get special attributes
|
||||
# so that our builder recognizes them, and the other builders are happy.
|
||||
code = '\n'.join(self.content)
|
||||
@ -92,20 +101,20 @@ class TestDirective(Directive):
|
||||
option_strings = self.options['options'].replace(',', ' ').split()
|
||||
for option in option_strings:
|
||||
if (option[0] not in '+-' or option[1:] not in
|
||||
doctest.OPTIONFLAGS_BY_NAME):
|
||||
doctest.OPTIONFLAGS_BY_NAME): # type: ignore
|
||||
# XXX warn?
|
||||
continue
|
||||
flag = doctest.OPTIONFLAGS_BY_NAME[option[1:]]
|
||||
flag = doctest.OPTIONFLAGS_BY_NAME[option[1:]] # type: ignore
|
||||
node['options'][flag] = (option[0] == '+')
|
||||
return [node]
|
||||
|
||||
|
||||
class TestsetupDirective(TestDirective):
|
||||
option_spec = {}
|
||||
option_spec = {} # type: Dict
|
||||
|
||||
|
||||
class TestcleanupDirective(TestDirective):
|
||||
option_spec = {}
|
||||
option_spec = {} # type: Dict
|
||||
|
||||
|
||||
class DoctestDirective(TestDirective):
|
||||
@ -128,19 +137,21 @@ class TestoutputDirective(TestDirective):
|
||||
}
|
||||
|
||||
|
||||
parser = doctest.DocTestParser()
|
||||
parser = doctest.DocTestParser() # type: ignore
|
||||
|
||||
|
||||
# helper classes
|
||||
|
||||
class TestGroup(object):
|
||||
def __init__(self, name):
|
||||
# type: (unicode) -> None
|
||||
self.name = name
|
||||
self.setup = []
|
||||
self.tests = []
|
||||
self.cleanup = []
|
||||
self.setup = [] # type: List[TestCode]
|
||||
self.tests = [] # type: List[List[TestCode]]
|
||||
self.cleanup = [] # type: List[TestCode]
|
||||
|
||||
def add_code(self, code, prepend=False):
|
||||
# type: (TestCode, bool) -> None
|
||||
if code.type == 'testsetup':
|
||||
if prepend:
|
||||
self.setup.insert(0, code)
|
||||
@ -158,30 +169,34 @@ class TestGroup(object):
|
||||
else:
|
||||
raise RuntimeError('invalid TestCode type')
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self): # type: ignore
|
||||
# type: () -> unicode
|
||||
return 'TestGroup(name=%r, setup=%r, cleanup=%r, tests=%r)' % (
|
||||
self.name, self.setup, self.cleanup, self.tests)
|
||||
|
||||
|
||||
class TestCode(object):
|
||||
def __init__(self, code, type, lineno, options=None):
|
||||
# type: (unicode, unicode, int, Dict) -> None
|
||||
self.code = code
|
||||
self.type = type
|
||||
self.lineno = lineno
|
||||
self.options = options or {}
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self): # type: ignore
|
||||
# type: () -> unicode
|
||||
return 'TestCode(%r, %r, %r, options=%r)' % (
|
||||
self.code, self.type, self.lineno, self.options)
|
||||
|
||||
|
||||
class SphinxDocTestRunner(doctest.DocTestRunner):
|
||||
class SphinxDocTestRunner(doctest.DocTestRunner): # type: ignore
|
||||
def summarize(self, out, verbose=None):
|
||||
# type: (Callable, bool) -> Tuple[int, int]
|
||||
string_io = StringIO()
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = string_io
|
||||
try:
|
||||
res = doctest.DocTestRunner.summarize(self, verbose)
|
||||
res = doctest.DocTestRunner.summarize(self, verbose) # type: ignore
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
out(string_io.getvalue())
|
||||
@ -189,6 +204,7 @@ class SphinxDocTestRunner(doctest.DocTestRunner):
|
||||
|
||||
def _DocTestRunner__patched_linecache_getlines(self, filename,
|
||||
module_globals=None):
|
||||
# type: (unicode, Any) -> Any
|
||||
# this is overridden from DocTestRunner adding the try-except below
|
||||
m = self._DocTestRunner__LINECACHE_FILENAME_RE.match(filename)
|
||||
if m and m.group('name') == self.test.name:
|
||||
@ -213,6 +229,7 @@ class DocTestBuilder(Builder):
|
||||
name = 'doctest'
|
||||
|
||||
def init(self):
|
||||
# type: () -> None
|
||||
# default options
|
||||
self.opt = self.config.doctest_default_flags
|
||||
|
||||
@ -221,7 +238,7 @@ class DocTestBuilder(Builder):
|
||||
# for doctest examples but unusable for multi-statement code such
|
||||
# as setup code -- to be able to use doctest error reporting with
|
||||
# that code nevertheless, we monkey-patch the "compile" it uses.
|
||||
doctest.compile = self.compile
|
||||
doctest.compile = self.compile # type: ignore
|
||||
|
||||
sys.path[0:0] = self.config.doctest_path
|
||||
|
||||
@ -236,7 +253,8 @@ class DocTestBuilder(Builder):
|
||||
|
||||
date = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
self.outfile = codecs.open(path.join(self.outdir, 'output.txt'),
|
||||
self.outfile = None # type: IO
|
||||
self.outfile = codecs.open(path.join(self.outdir, 'output.txt'), # type: ignore
|
||||
'w', encoding='utf-8')
|
||||
self.outfile.write('''\
|
||||
Results of doctest builder run on %s
|
||||
@ -244,10 +262,12 @@ Results of doctest builder run on %s
|
||||
''' % (date, '='*len(date)))
|
||||
|
||||
def _out(self, text):
|
||||
# type: (unicode) -> None
|
||||
self.info(text, nonl=True)
|
||||
self.outfile.write(text)
|
||||
|
||||
def _warn_out(self, text):
|
||||
# type: (unicode) -> None
|
||||
if self.app.quiet or self.app.warningiserror:
|
||||
self.warn(text)
|
||||
else:
|
||||
@ -257,14 +277,18 @@ Results of doctest builder run on %s
|
||||
self.outfile.write(text)
|
||||
|
||||
def get_target_uri(self, docname, typ=None):
|
||||
# type: (unicode, unicode) -> unicode
|
||||
return ''
|
||||
|
||||
def get_outdated_docs(self):
|
||||
# type: () -> Set[unicode]
|
||||
return self.env.found_docs
|
||||
|
||||
def finish(self):
|
||||
# type: () -> None
|
||||
# write executive summary
|
||||
def s(v):
|
||||
# type: (int) -> unicode
|
||||
return v != 1 and 's' or ''
|
||||
repl = (self.total_tries, s(self.total_tries),
|
||||
self.total_failures, s(self.total_failures),
|
||||
@ -284,6 +308,7 @@ Doctest summary
|
||||
self.app.statuscode = 1
|
||||
|
||||
def write(self, build_docnames, updated_docnames, method='update'):
|
||||
# type: (Iterable[unicode], Sequence[unicode], unicode) -> None
|
||||
if build_docnames is None:
|
||||
build_docnames = sorted(self.env.all_docs)
|
||||
|
||||
@ -294,7 +319,8 @@ Doctest summary
|
||||
self.test_doc(docname, doctree)
|
||||
|
||||
def test_doc(self, docname, doctree):
|
||||
groups = {}
|
||||
# type: (unicode, nodes.Node) -> None
|
||||
groups = {} # type: Dict[unicode, TestGroup]
|
||||
add_to_all_groups = []
|
||||
self.setup_runner = SphinxDocTestRunner(verbose=False,
|
||||
optionflags=self.opt)
|
||||
@ -308,11 +334,13 @@ Doctest summary
|
||||
|
||||
if self.config.doctest_test_doctest_blocks:
|
||||
def condition(node):
|
||||
# type: (nodes.Node) -> bool
|
||||
return (isinstance(node, (nodes.literal_block, nodes.comment)) and
|
||||
'testnodetype' in node) or \
|
||||
isinstance(node, nodes.doctest_block)
|
||||
else:
|
||||
def condition(node):
|
||||
# type: (nodes.Node) -> bool
|
||||
return isinstance(node, (nodes.literal_block, nodes.comment)) \
|
||||
and 'testnodetype' in node
|
||||
for node in doctree.traverse(condition):
|
||||
@ -366,26 +394,29 @@ Doctest summary
|
||||
self.cleanup_tries += res_t
|
||||
|
||||
def compile(self, code, name, type, flags, dont_inherit):
|
||||
# type: (unicode, unicode, unicode, Any, bool) -> Any
|
||||
return compile(code, name, self.type, flags, dont_inherit)
|
||||
|
||||
def test_group(self, group, filename):
|
||||
# type: (TestGroup, unicode) -> None
|
||||
if PY2:
|
||||
filename_str = filename.encode(fs_encoding)
|
||||
else:
|
||||
filename_str = filename
|
||||
|
||||
ns = {}
|
||||
ns = {} # type: Dict
|
||||
|
||||
def run_setup_cleanup(runner, testcodes, what):
|
||||
# type: (Any, List[TestCode], Any) -> bool
|
||||
examples = []
|
||||
for testcode in testcodes:
|
||||
examples.append(doctest.Example(
|
||||
doctest_encode(testcode.code, self.env.config.source_encoding), '',
|
||||
examples.append(doctest.Example( # type: ignore
|
||||
doctest_encode(testcode.code, self.env.config.source_encoding), '', # type: ignore # NOQA
|
||||
lineno=testcode.lineno))
|
||||
if not examples:
|
||||
return True
|
||||
# simulate a doctest with the code
|
||||
sim_doctest = doctest.DocTest(examples, {},
|
||||
sim_doctest = doctest.DocTest(examples, {}, # type: ignore
|
||||
'%s (%s code)' % (group.name, what),
|
||||
filename_str, 0, None)
|
||||
sim_doctest.globs = ns
|
||||
@ -407,7 +438,7 @@ Doctest summary
|
||||
# ordinary doctests (code/output interleaved)
|
||||
try:
|
||||
test = parser.get_doctest(
|
||||
doctest_encode(code[0].code, self.env.config.source_encoding), {},
|
||||
doctest_encode(code[0].code, self.env.config.source_encoding), {}, # type: ignore # NOQA
|
||||
group.name, filename_str, code[0].lineno)
|
||||
except Exception:
|
||||
self.warn('ignoring invalid doctest code: %r' %
|
||||
@ -427,19 +458,19 @@ Doctest summary
|
||||
output = code[1] and code[1].code or ''
|
||||
options = code[1] and code[1].options or {}
|
||||
# disable <BLANKLINE> processing as it is not needed
|
||||
options[doctest.DONT_ACCEPT_BLANKLINE] = True
|
||||
options[doctest.DONT_ACCEPT_BLANKLINE] = True # type: ignore
|
||||
# find out if we're testing an exception
|
||||
m = parser._EXCEPTION_RE.match(output)
|
||||
if m:
|
||||
exc_msg = m.group('msg')
|
||||
else:
|
||||
exc_msg = None
|
||||
example = doctest.Example(
|
||||
doctest_encode(code[0].code, self.env.config.source_encoding), output,
|
||||
example = doctest.Example( # type: ignore
|
||||
doctest_encode(code[0].code, self.env.config.source_encoding), output, # type: ignore # NOQA
|
||||
exc_msg=exc_msg,
|
||||
lineno=code[0].lineno,
|
||||
options=options)
|
||||
test = doctest.DocTest([example], {}, group.name,
|
||||
test = doctest.DocTest([example], {}, group.name, # type: ignore
|
||||
filename_str, code[0].lineno, None)
|
||||
self.type = 'exec' # multiple statements again
|
||||
# DocTest.__init__ copies the globs namespace, which we don't want
|
||||
@ -452,6 +483,7 @@ Doctest summary
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.add_directive('testsetup', TestsetupDirective)
|
||||
app.add_directive('testcleanup', TestcleanupDirective)
|
||||
app.add_directive('doctest', DoctestDirective)
|
||||
@ -465,6 +497,6 @@ def setup(app):
|
||||
app.add_config_value('doctest_global_cleanup', '', False)
|
||||
app.add_config_value(
|
||||
'doctest_default_flags',
|
||||
doctest.DONT_ACCEPT_TRUE_FOR_1 | doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL,
|
||||
doctest.DONT_ACCEPT_TRUE_FOR_1 | doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL, # type: ignore # NOQA
|
||||
False)
|
||||
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
|
||||
|
@ -18,6 +18,7 @@ from subprocess import Popen, PIPE
|
||||
from hashlib import sha1
|
||||
|
||||
from six import text_type
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives
|
||||
from docutils.statemachine import ViewList
|
||||
@ -29,6 +30,11 @@ from sphinx.util.i18n import search_image_for_language
|
||||
from sphinx.util.osutil import ensuredir, ENOENT, EPIPE, EINVAL
|
||||
from sphinx.util.compat import Directive
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Tuple # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
|
||||
mapname_re = re.compile(r'<map id="(.*?)"')
|
||||
|
||||
@ -42,6 +48,7 @@ class graphviz(nodes.General, nodes.Inline, nodes.Element):
|
||||
|
||||
|
||||
def figure_wrapper(directive, node, caption):
|
||||
# type: (Directive, nodes.Node, unicode) -> nodes.figure
|
||||
figure_node = nodes.figure('', node)
|
||||
if 'align' in node:
|
||||
figure_node['align'] = node.attributes.pop('align')
|
||||
@ -58,6 +65,7 @@ def figure_wrapper(directive, node, caption):
|
||||
|
||||
|
||||
def align_spec(argument):
|
||||
# type: (Any) -> bool
|
||||
return directives.choice(argument, ('left', 'center', 'right'))
|
||||
|
||||
|
||||
@ -79,6 +87,7 @@ class Graphviz(Directive):
|
||||
}
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
if self.arguments:
|
||||
document = self.state.document
|
||||
if self.content:
|
||||
@ -140,6 +149,7 @@ class GraphvizSimple(Directive):
|
||||
}
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
node = graphviz()
|
||||
node['code'] = '%s %s {\n%s\n}\n' % \
|
||||
(self.name, self.arguments[0], '\n'.join(self.content))
|
||||
@ -162,6 +172,7 @@ class GraphvizSimple(Directive):
|
||||
|
||||
|
||||
def render_dot(self, code, options, format, prefix='graphviz'):
|
||||
# type: (nodes.NodeVisitor, unicode, Dict, unicode, unicode) -> Tuple[unicode, unicode]
|
||||
"""Render graphviz code into a PNG or PDF output file."""
|
||||
graphviz_dot = options.get('graphviz_dot', self.builder.config.graphviz_dot)
|
||||
hashkey = (code + str(options) + str(graphviz_dot) +
|
||||
@ -221,6 +232,7 @@ def render_dot(self, code, options, format, prefix='graphviz'):
|
||||
|
||||
|
||||
def warn_for_deprecated_option(self, node):
|
||||
# type: (nodes.NodeVisitor, graphviz) -> None
|
||||
if hasattr(self.builder, '_graphviz_warned_inline'):
|
||||
return
|
||||
|
||||
@ -231,6 +243,7 @@ def warn_for_deprecated_option(self, node):
|
||||
|
||||
def render_dot_html(self, node, code, options, prefix='graphviz',
|
||||
imgcls=None, alt=None):
|
||||
# type: (nodes.NodeVisitor, graphviz, unicode, Dict, unicode, unicode, unicode) -> Tuple[unicode, unicode] # NOQA
|
||||
format = self.builder.config.graphviz_output_format
|
||||
try:
|
||||
if format not in ('png', 'svg'):
|
||||
@ -263,7 +276,7 @@ def render_dot_html(self, node, code, options, prefix='graphviz',
|
||||
(fname, alt, imgcss))
|
||||
else:
|
||||
# has a map: get the name of the map and connect the parts
|
||||
mapname = mapname_re.match(imgmap[0].decode('utf-8')).group(1)
|
||||
mapname = mapname_re.match(imgmap[0].decode('utf-8')).group(1) # type: ignore
|
||||
self.body.append('<img src="%s" alt="%s" usemap="#%s" %s/>\n' %
|
||||
(fname, alt, mapname, imgcss))
|
||||
self.body.extend([item.decode('utf-8') for item in imgmap])
|
||||
@ -274,11 +287,13 @@ def render_dot_html(self, node, code, options, prefix='graphviz',
|
||||
|
||||
|
||||
def html_visit_graphviz(self, node):
|
||||
# type: (nodes.NodeVisitor, graphviz) -> None
|
||||
warn_for_deprecated_option(self, node)
|
||||
render_dot_html(self, node, node['code'], node['options'])
|
||||
|
||||
|
||||
def render_dot_latex(self, node, code, options, prefix='graphviz'):
|
||||
# type: (nodes.NodeVisitor, graphviz, unicode, Dict, unicode) -> None
|
||||
try:
|
||||
fname, outfn = render_dot(self, code, options, 'pdf', prefix)
|
||||
except GraphvizError as exc:
|
||||
@ -292,7 +307,7 @@ def render_dot_latex(self, node, code, options, prefix='graphviz'):
|
||||
para_separator = '\n'
|
||||
|
||||
if fname is not None:
|
||||
post = None
|
||||
post = None # type: unicode
|
||||
if not is_inline and 'align' in node:
|
||||
if node['align'] == 'left':
|
||||
self.body.append('{')
|
||||
@ -309,11 +324,13 @@ def render_dot_latex(self, node, code, options, prefix='graphviz'):
|
||||
|
||||
|
||||
def latex_visit_graphviz(self, node):
|
||||
# type: (nodes.NodeVisitor, graphviz) -> None
|
||||
warn_for_deprecated_option(self, node)
|
||||
render_dot_latex(self, node, node['code'], node['options'])
|
||||
|
||||
|
||||
def render_dot_texinfo(self, node, code, options, prefix='graphviz'):
|
||||
# type: (nodes.NodeVisitor, graphviz, unicode, Dict, unicode) -> None
|
||||
try:
|
||||
fname, outfn = render_dot(self, code, options, 'png', prefix)
|
||||
except GraphvizError as exc:
|
||||
@ -325,11 +342,13 @@ def render_dot_texinfo(self, node, code, options, prefix='graphviz'):
|
||||
|
||||
|
||||
def texinfo_visit_graphviz(self, node):
|
||||
# type: (nodes.NodeVisitor, graphviz) -> None
|
||||
warn_for_deprecated_option(self, node)
|
||||
render_dot_texinfo(self, node, node['code'], node['options'])
|
||||
|
||||
|
||||
def text_visit_graphviz(self, node):
|
||||
# type: (nodes.NodeVisitor, graphviz) -> None
|
||||
warn_for_deprecated_option(self, node)
|
||||
if 'alt' in node.attributes:
|
||||
self.add_text(_('[graph: %s]') % node['alt'])
|
||||
@ -339,6 +358,7 @@ def text_visit_graphviz(self, node):
|
||||
|
||||
|
||||
def man_visit_graphviz(self, node):
|
||||
# type: (nodes.NodeVisitor, graphviz) -> None
|
||||
warn_for_deprecated_option(self, node)
|
||||
if 'alt' in node.attributes:
|
||||
self.body.append(_('[graph: %s]') % node['alt'])
|
||||
@ -348,6 +368,7 @@ def man_visit_graphviz(self, node):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.add_node(graphviz,
|
||||
html=(html_visit_graphviz, None),
|
||||
latex=(latex_visit_graphviz, None),
|
||||
|
@ -26,6 +26,11 @@ import sphinx
|
||||
from sphinx.util.nodes import set_source_info
|
||||
from sphinx.util.compat import Directive
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
|
||||
class ifconfig(nodes.Element):
|
||||
pass
|
||||
@ -37,9 +42,10 @@ class IfConfig(Directive):
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
option_spec = {}
|
||||
option_spec = {} # type: Dict
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
node = ifconfig()
|
||||
node.document = self.state.document
|
||||
set_source_info(self, node)
|
||||
@ -50,16 +56,17 @@ class IfConfig(Directive):
|
||||
|
||||
|
||||
def process_ifconfig_nodes(app, doctree, docname):
|
||||
# type: (Sphinx, nodes.Node, unicode) -> None
|
||||
ns = dict((k, app.config[k]) for k in app.config.values)
|
||||
ns.update(app.config.__dict__.copy())
|
||||
ns['builder'] = app.builder.name
|
||||
for node in doctree.traverse(ifconfig):
|
||||
try:
|
||||
res = eval(node['expr'], ns)
|
||||
res = eval(node['expr'], ns) # type: ignore
|
||||
except Exception as err:
|
||||
# handle exceptions in a clean fashion
|
||||
from traceback import format_exception_only
|
||||
msg = ''.join(format_exception_only(err.__class__, err))
|
||||
msg = ''.join(format_exception_only(err.__class__, err)) # type: ignore
|
||||
newnode = doctree.reporter.error('Exception occured in '
|
||||
'ifconfig expression: \n%s' %
|
||||
msg, base_node=node)
|
||||
@ -72,6 +79,7 @@ def process_ifconfig_nodes(app, doctree, docname):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.add_node(ifconfig)
|
||||
app.add_directive('ifconfig', IfConfig)
|
||||
app.connect('doctree-resolved', process_ifconfig_nodes)
|
||||
|
@ -19,6 +19,7 @@ from subprocess import Popen, PIPE
|
||||
from hashlib import sha1
|
||||
|
||||
from six import text_type
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
import sphinx
|
||||
@ -29,11 +30,18 @@ from sphinx.util.osutil import ensuredir, ENOENT, cd
|
||||
from sphinx.util.pycompat import sys_encoding
|
||||
from sphinx.ext.mathbase import setup_math as mathbase_setup, wrap_displaymath
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Tuple # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.ext.mathbase import math as math_node, displaymath # NOQA
|
||||
|
||||
|
||||
class MathExtError(SphinxError):
|
||||
category = 'Math extension error'
|
||||
|
||||
def __init__(self, msg, stderr=None, stdout=None):
|
||||
# type: (unicode, unicode, unicode) -> None
|
||||
if stderr:
|
||||
msg += '\n[stderr]\n' + stderr.decode(sys_encoding, 'replace')
|
||||
if stdout:
|
||||
@ -72,6 +80,7 @@ depth_re = re.compile(br'\[\d+ depth=(-?\d+)\]')
|
||||
|
||||
|
||||
def render_math(self, math):
|
||||
# type: (nodes.NodeVisitor, unicode) -> Tuple[unicode, int]
|
||||
"""Render the LaTeX math expression *math* using latex and dvipng or
|
||||
dvisvgm.
|
||||
|
||||
@ -116,9 +125,8 @@ def render_math(self, math):
|
||||
else:
|
||||
tempdir = self.builder._imgmath_tempdir
|
||||
|
||||
tf = codecs.open(path.join(tempdir, 'math.tex'), 'w', 'utf-8')
|
||||
tf.write(latex)
|
||||
tf.close()
|
||||
with codecs.open(path.join(tempdir, 'math.tex'), 'w', 'utf-8') as tf: # type: ignore
|
||||
tf.write(latex)
|
||||
|
||||
# build latex command; old versions of latex don't have the
|
||||
# --output-directory option, so we have to manually chdir to the
|
||||
@ -199,23 +207,26 @@ def render_math(self, math):
|
||||
|
||||
|
||||
def cleanup_tempdir(app, exc):
|
||||
# type: (Sphinx, Exception) -> None
|
||||
if exc:
|
||||
return
|
||||
if not hasattr(app.builder, '_imgmath_tempdir'):
|
||||
return
|
||||
try:
|
||||
shutil.rmtree(app.builder._mathpng_tempdir)
|
||||
shutil.rmtree(app.builder._mathpng_tempdir) # type: ignore
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def get_tooltip(self, node):
|
||||
# type: (nodes.NodeVisitor, math_node) -> unicode
|
||||
if self.builder.config.imgmath_add_tooltips:
|
||||
return ' alt="%s"' % self.encode(node['latex']).strip()
|
||||
return ''
|
||||
|
||||
|
||||
def html_visit_math(self, node):
|
||||
# type: (nodes.NodeVisitor, math_node) -> None
|
||||
try:
|
||||
fname, depth = render_math(self, '$'+node['latex']+'$')
|
||||
except MathExtError as exc:
|
||||
@ -238,6 +249,7 @@ def html_visit_math(self, node):
|
||||
|
||||
|
||||
def html_visit_displaymath(self, node):
|
||||
# type: (nodes.NodeVisitor, displaymath) -> None
|
||||
if node['nowrap']:
|
||||
latex = node['latex']
|
||||
else:
|
||||
@ -268,6 +280,7 @@ def html_visit_displaymath(self, node):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
try:
|
||||
mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None))
|
||||
except ExtensionError:
|
||||
|
@ -42,10 +42,10 @@ import inspect
|
||||
try:
|
||||
from hashlib import md5
|
||||
except ImportError:
|
||||
from md5 import md5
|
||||
from md5 import md5 # type: ignore
|
||||
|
||||
from six import text_type
|
||||
from six.moves import builtins
|
||||
from six.moves import builtins # type: ignore
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives
|
||||
@ -57,6 +57,12 @@ from sphinx.pycode import ModuleAnalyzer
|
||||
from sphinx.util import force_decode
|
||||
from sphinx.util.compat import Directive
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Tuple # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
|
||||
|
||||
class_sig_re = re.compile(r'''^([\w.]*\.)? # module names
|
||||
(\w+) \s* $ # class/final module name
|
||||
@ -75,6 +81,7 @@ class InheritanceGraph(object):
|
||||
"""
|
||||
def __init__(self, class_names, currmodule, show_builtins=False,
|
||||
private_bases=False, parts=0):
|
||||
# type: (unicode, str, bool, bool, int) -> None
|
||||
"""*class_names* is a list of child classes to show bases from.
|
||||
|
||||
If *show_builtins* is True, then Python builtins will be shown
|
||||
@ -89,9 +96,10 @@ class InheritanceGraph(object):
|
||||
'inheritance diagram')
|
||||
|
||||
def _import_class_or_module(self, name, currmodule):
|
||||
# type: (unicode, str) -> Any
|
||||
"""Import a class using its fully-qualified *name*."""
|
||||
try:
|
||||
path, base = class_sig_re.match(name).groups()
|
||||
path, base = class_sig_re.match(name).groups() # type: ignore
|
||||
except (AttributeError, ValueError):
|
||||
raise InheritanceException('Invalid class or module %r specified '
|
||||
'for inheritance diagram' % name)
|
||||
@ -126,7 +134,7 @@ class InheritanceGraph(object):
|
||||
return [todoc]
|
||||
elif inspect.ismodule(todoc):
|
||||
classes = []
|
||||
for cls in todoc.__dict__.values():
|
||||
for cls in todoc.__dict__.values(): # type: ignore
|
||||
if inspect.isclass(cls) and cls.__module__ == todoc.__name__:
|
||||
classes.append(cls)
|
||||
return classes
|
||||
@ -134,13 +142,15 @@ class InheritanceGraph(object):
|
||||
'not a class or module' % name)
|
||||
|
||||
def _import_classes(self, class_names, currmodule):
|
||||
# type: (unicode, str) -> List[Any]
|
||||
"""Import a list of classes."""
|
||||
classes = []
|
||||
classes = [] # type: List[Any]
|
||||
for name in class_names:
|
||||
classes.extend(self._import_class_or_module(name, currmodule))
|
||||
return classes
|
||||
|
||||
def _class_info(self, classes, show_builtins, private_bases, parts):
|
||||
# type: (List[Any], bool, bool, int) -> List[Tuple[unicode, unicode, List[unicode], unicode]] # NOQA
|
||||
"""Return name and bases for all classes that are ancestors of
|
||||
*classes*.
|
||||
|
||||
@ -151,6 +161,7 @@ class InheritanceGraph(object):
|
||||
py_builtins = vars(builtins).values()
|
||||
|
||||
def recurse(cls):
|
||||
# type: (Any) -> None
|
||||
if not show_builtins and cls in py_builtins:
|
||||
return
|
||||
if not private_bases and cls.__name__.startswith('_'):
|
||||
@ -172,7 +183,7 @@ class InheritanceGraph(object):
|
||||
except Exception: # might raise AttributeError for strange classes
|
||||
pass
|
||||
|
||||
baselist = []
|
||||
baselist = [] # type: List[unicode]
|
||||
all_classes[cls] = (nodename, fullname, baselist, tooltip)
|
||||
for base in cls.__bases__:
|
||||
if not show_builtins and base in py_builtins:
|
||||
@ -189,6 +200,7 @@ class InheritanceGraph(object):
|
||||
return list(all_classes.values())
|
||||
|
||||
def class_name(self, cls, parts=0):
|
||||
# type: (Any, int) -> unicode
|
||||
"""Given a class object, return a fully-qualified name.
|
||||
|
||||
This works for things I've tested in matplotlib so far, but may not be
|
||||
@ -205,8 +217,9 @@ class InheritanceGraph(object):
|
||||
return '.'.join(name_parts[-parts:])
|
||||
|
||||
def get_all_class_names(self):
|
||||
# type: () -> List[unicode]
|
||||
"""Get all of the class names involved in the graph."""
|
||||
return [fullname for (_, fullname, _, _) in self.class_info]
|
||||
return [fullname for (_, fullname, _, _) in self.class_info] # type: ignore
|
||||
|
||||
# These are the default attrs for graphviz
|
||||
default_graph_attrs = {
|
||||
@ -227,13 +240,16 @@ class InheritanceGraph(object):
|
||||
}
|
||||
|
||||
def _format_node_attrs(self, attrs):
|
||||
# type: (Dict) -> unicode
|
||||
return ','.join(['%s=%s' % x for x in sorted(attrs.items())])
|
||||
|
||||
def _format_graph_attrs(self, attrs):
|
||||
# type: (Dict) -> unicode
|
||||
return ''.join(['%s=%s;\n' % x for x in sorted(attrs.items())])
|
||||
|
||||
def generate_dot(self, name, urls={}, env=None,
|
||||
graph_attrs={}, node_attrs={}, edge_attrs={}):
|
||||
# type: (unicode, Dict, BuildEnvironment, Dict, Dict, Dict) -> unicode
|
||||
"""Generate a graphviz dot graph from the classes that were passed in
|
||||
to __init__.
|
||||
|
||||
@ -255,7 +271,7 @@ class InheritanceGraph(object):
|
||||
n_attrs.update(env.config.inheritance_node_attrs)
|
||||
e_attrs.update(env.config.inheritance_edge_attrs)
|
||||
|
||||
res = []
|
||||
res = [] # type: List[unicode]
|
||||
res.append('digraph %s {\n' % name)
|
||||
res.append(self._format_graph_attrs(g_attrs))
|
||||
|
||||
@ -301,6 +317,7 @@ class InheritanceDiagram(Directive):
|
||||
}
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
node = inheritance_diagram()
|
||||
node.document = self.state.document
|
||||
env = self.state.document.settings.env
|
||||
@ -340,11 +357,13 @@ class InheritanceDiagram(Directive):
|
||||
|
||||
|
||||
def get_graph_hash(node):
|
||||
# type: (inheritance_diagram) -> unicode
|
||||
encoded = (node['content'] + str(node['parts'])).encode('utf-8')
|
||||
return md5(encoded).hexdigest()[-10:]
|
||||
|
||||
|
||||
def html_visit_inheritance_diagram(self, node):
|
||||
# type: (nodes.NodeVisitor, inheritance_diagram) -> None
|
||||
"""
|
||||
Output the graph for HTML. This will insert a PNG with clickable
|
||||
image map.
|
||||
@ -377,6 +396,7 @@ def html_visit_inheritance_diagram(self, node):
|
||||
|
||||
|
||||
def latex_visit_inheritance_diagram(self, node):
|
||||
# type: (nodes.NodeVisitor, inheritance_diagram) -> None
|
||||
"""
|
||||
Output the graph for LaTeX. This will insert a PDF.
|
||||
"""
|
||||
@ -392,6 +412,7 @@ def latex_visit_inheritance_diagram(self, node):
|
||||
|
||||
|
||||
def texinfo_visit_inheritance_diagram(self, node):
|
||||
# type: (nodes.NodeVisitor, inheritance_diagram) -> None
|
||||
"""
|
||||
Output the graph for Texinfo. This will insert a PNG.
|
||||
"""
|
||||
@ -407,10 +428,12 @@ def texinfo_visit_inheritance_diagram(self, node):
|
||||
|
||||
|
||||
def skip(self, node):
|
||||
# type: (nodes.NodeVisitor, inheritance_diagram) -> None
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.setup_extension('sphinx.ext.graphviz')
|
||||
app.add_node(
|
||||
inheritance_diagram,
|
||||
|
@ -33,8 +33,9 @@ import posixpath
|
||||
from os import path
|
||||
import re
|
||||
|
||||
from six import iteritems, string_types
|
||||
from six import PY3, iteritems, string_types
|
||||
from six.moves.urllib.parse import urlsplit, urlunsplit
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.utils import relative_path
|
||||
|
||||
@ -43,13 +44,25 @@ from sphinx.locale import _
|
||||
from sphinx.builders.html import INVENTORY_FILENAME
|
||||
from sphinx.util.requests import requests, useragent_header
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Dict, IO, Iterator, Tuple, Union # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
|
||||
if PY3:
|
||||
unicode = str
|
||||
|
||||
Inventory = Dict[unicode, Dict[unicode, Tuple[unicode, unicode, unicode, unicode]]]
|
||||
|
||||
|
||||
UTF8StreamReader = codecs.lookup('utf-8')[2]
|
||||
|
||||
|
||||
def read_inventory_v1(f, uri, join):
|
||||
# type: (IO, unicode, Callable) -> Inventory
|
||||
f = UTF8StreamReader(f)
|
||||
invdata = {}
|
||||
invdata = {} # type: Inventory
|
||||
line = next(f)
|
||||
projname = line.rstrip()[11:]
|
||||
line = next(f)
|
||||
@ -69,7 +82,8 @@ def read_inventory_v1(f, uri, join):
|
||||
|
||||
|
||||
def read_inventory_v2(f, uri, join, bufsize=16*1024):
|
||||
invdata = {}
|
||||
# type: (IO, unicode, Callable, int) -> Inventory
|
||||
invdata = {} # type: Inventory
|
||||
line = f.readline()
|
||||
projname = line.rstrip()[11:].decode('utf-8')
|
||||
line = f.readline()
|
||||
@ -79,12 +93,14 @@ def read_inventory_v2(f, uri, join, bufsize=16*1024):
|
||||
raise ValueError
|
||||
|
||||
def read_chunks():
|
||||
# type: () -> Iterator[bytes]
|
||||
decompressor = zlib.decompressobj()
|
||||
for chunk in iter(lambda: f.read(bufsize), b''):
|
||||
yield decompressor.decompress(chunk)
|
||||
yield decompressor.flush()
|
||||
|
||||
def split_lines(iter):
|
||||
# type: (Iterator[bytes]) -> Iterator[unicode]
|
||||
buf = b''
|
||||
for chunk in iter:
|
||||
buf += chunk
|
||||
@ -117,6 +133,7 @@ def read_inventory_v2(f, uri, join, bufsize=16*1024):
|
||||
|
||||
|
||||
def read_inventory(f, uri, join, bufsize=16*1024):
|
||||
# type: (IO, unicode, Callable, int) -> Inventory
|
||||
line = f.readline().rstrip().decode('utf-8')
|
||||
if line == '# Sphinx inventory version 1':
|
||||
return read_inventory_v1(f, uri, join)
|
||||
@ -125,6 +142,7 @@ def read_inventory(f, uri, join, bufsize=16*1024):
|
||||
|
||||
|
||||
def _strip_basic_auth(url):
|
||||
# type: (unicode) -> unicode
|
||||
"""Returns *url* with basic auth credentials removed. Also returns the
|
||||
basic auth username and password if they're present in *url*.
|
||||
|
||||
@ -146,6 +164,7 @@ def _strip_basic_auth(url):
|
||||
|
||||
|
||||
def _read_from_url(url, timeout=None):
|
||||
# type: (unicode, int) -> IO
|
||||
"""Reads data from *url* with an HTTP *GET*.
|
||||
|
||||
This function supports fetching from resources which use basic HTTP auth as
|
||||
@ -168,6 +187,7 @@ def _read_from_url(url, timeout=None):
|
||||
|
||||
|
||||
def _get_safe_url(url):
|
||||
# type: (unicode) -> unicode
|
||||
"""Gets version of *url* with basic auth passwords obscured. This function
|
||||
returns results suitable for printing and logging.
|
||||
|
||||
@ -193,6 +213,7 @@ def _get_safe_url(url):
|
||||
|
||||
|
||||
def fetch_inventory(app, uri, inv):
|
||||
# type: (Sphinx, unicode, Any) -> Any
|
||||
"""Fetch, parse and return an intersphinx inventory file."""
|
||||
# both *uri* (base URI of the links to generate) and *inv* (actual
|
||||
# location of the inventory file) can be local or remote URIs
|
||||
@ -211,7 +232,7 @@ def fetch_inventory(app, uri, inv):
|
||||
return
|
||||
try:
|
||||
if hasattr(f, 'url'):
|
||||
newinv = f.url
|
||||
newinv = f.url # type: ignore
|
||||
if inv != newinv:
|
||||
app.info('intersphinx inventory has moved: %s -> %s' % (inv, newinv))
|
||||
|
||||
@ -231,17 +252,22 @@ def fetch_inventory(app, uri, inv):
|
||||
|
||||
|
||||
def load_mappings(app):
|
||||
# type: (Sphinx) -> None
|
||||
"""Load all intersphinx mappings into the environment."""
|
||||
now = int(time.time())
|
||||
cache_time = now - app.config.intersphinx_cache_limit * 86400
|
||||
env = app.builder.env
|
||||
if not hasattr(env, 'intersphinx_cache'):
|
||||
env.intersphinx_cache = {}
|
||||
env.intersphinx_inventory = {}
|
||||
env.intersphinx_named_inventory = {}
|
||||
cache = env.intersphinx_cache
|
||||
env.intersphinx_cache = {} # type: ignore
|
||||
env.intersphinx_inventory = {} # type: ignore
|
||||
env.intersphinx_named_inventory = {} # type: ignore
|
||||
cache = env.intersphinx_cache # type: ignore
|
||||
update = False
|
||||
for key, value in iteritems(app.config.intersphinx_mapping):
|
||||
name = None # type: unicode
|
||||
uri = None # type: unicode
|
||||
inv = None # type: Union[unicode, Tuple[unicode, ...]]
|
||||
|
||||
if isinstance(value, tuple):
|
||||
# new format
|
||||
name, (uri, inv) = key, value
|
||||
@ -257,7 +283,7 @@ def load_mappings(app):
|
||||
if not isinstance(inv, tuple):
|
||||
invs = (inv, )
|
||||
else:
|
||||
invs = inv
|
||||
invs = inv # type: ignore
|
||||
|
||||
for inv in invs:
|
||||
if not inv:
|
||||
@ -266,7 +292,7 @@ def load_mappings(app):
|
||||
# files; remote ones only if the cache time is expired
|
||||
if '://' not in inv or uri not in cache \
|
||||
or cache[uri][1] < cache_time:
|
||||
safe_inv_url = _get_safe_url(inv)
|
||||
safe_inv_url = _get_safe_url(inv) # type: ignore
|
||||
app.info(
|
||||
'loading intersphinx inventory from %s...' % safe_inv_url)
|
||||
invdata = fetch_inventory(app, uri, inv)
|
||||
@ -276,8 +302,8 @@ def load_mappings(app):
|
||||
break
|
||||
|
||||
if update:
|
||||
env.intersphinx_inventory = {}
|
||||
env.intersphinx_named_inventory = {}
|
||||
env.intersphinx_inventory = {} # type: ignore
|
||||
env.intersphinx_named_inventory = {} # type: ignore
|
||||
# Duplicate values in different inventories will shadow each
|
||||
# other; which one will override which can vary between builds
|
||||
# since they are specified using an unordered dict. To make
|
||||
@ -290,15 +316,17 @@ def load_mappings(app):
|
||||
unnamed_vals = [v for v in cached_vals if not v[0]]
|
||||
for name, _x, invdata in named_vals + unnamed_vals:
|
||||
if name:
|
||||
env.intersphinx_named_inventory[name] = invdata
|
||||
env.intersphinx_named_inventory[name] = invdata # type: ignore
|
||||
for type, objects in iteritems(invdata):
|
||||
env.intersphinx_inventory.setdefault(
|
||||
env.intersphinx_inventory.setdefault( # type: ignore
|
||||
type, {}).update(objects)
|
||||
|
||||
|
||||
def missing_reference(app, env, node, contnode):
|
||||
# type: (Sphinx, BuildEnvironment, nodes.Node, nodes.Node) -> None
|
||||
"""Attempt to resolve a missing reference via intersphinx references."""
|
||||
target = node['reftarget']
|
||||
objtypes = None # type: List[unicode]
|
||||
if node['reftype'] == 'any':
|
||||
# we search anything!
|
||||
objtypes = ['%s:%s' % (domain.name, objtype)
|
||||
@ -317,14 +345,14 @@ def missing_reference(app, env, node, contnode):
|
||||
if not objtypes:
|
||||
return
|
||||
objtypes = ['%s:%s' % (domain, objtype) for objtype in objtypes]
|
||||
to_try = [(env.intersphinx_inventory, target)]
|
||||
to_try = [(env.intersphinx_inventory, target)] # type: ignore
|
||||
in_set = None
|
||||
if ':' in target:
|
||||
# first part may be the foreign doc set name
|
||||
setname, newtarget = target.split(':', 1)
|
||||
if setname in env.intersphinx_named_inventory:
|
||||
if setname in env.intersphinx_named_inventory: # type: ignore
|
||||
in_set = setname
|
||||
to_try.append((env.intersphinx_named_inventory[setname], newtarget))
|
||||
to_try.append((env.intersphinx_named_inventory[setname], newtarget)) # type: ignore # NOQA
|
||||
for inventory, target in to_try:
|
||||
for objtype in objtypes:
|
||||
if objtype not in inventory or target not in inventory[objtype]:
|
||||
@ -358,6 +386,7 @@ def missing_reference(app, env, node, contnode):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.add_config_value('intersphinx_mapping', {}, True)
|
||||
app.add_config_value('intersphinx_cache_limit', 5, False)
|
||||
app.add_config_value('intersphinx_timeout', None, False)
|
||||
@ -377,7 +406,7 @@ if __name__ == '__main__':
|
||||
print(msg, file=sys.stderr)
|
||||
|
||||
filename = sys.argv[1]
|
||||
invdata = fetch_inventory(MockApp(), '', filename)
|
||||
invdata = fetch_inventory(MockApp(), '', filename) # type: ignore
|
||||
for key in sorted(invdata or {}):
|
||||
print(key)
|
||||
for entry, einfo in sorted(invdata[key].items()):
|
||||
|
@ -16,12 +16,18 @@ from sphinx import addnodes
|
||||
from sphinx.locale import _
|
||||
from sphinx.errors import SphinxError
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
|
||||
class LinkcodeError(SphinxError):
|
||||
category = "linkcode error"
|
||||
|
||||
|
||||
def doctree_read(app, doctree):
|
||||
# type: (Sphinx, nodes.Node) -> None
|
||||
env = app.builder.env
|
||||
|
||||
resolve_target = getattr(env.config, 'linkcode_resolve', None)
|
||||
@ -38,7 +44,7 @@ def doctree_read(app, doctree):
|
||||
|
||||
for objnode in doctree.traverse(addnodes.desc):
|
||||
domain = objnode.get('domain')
|
||||
uris = set()
|
||||
uris = set() # type: Set[unicode]
|
||||
for signode in objnode:
|
||||
if not isinstance(signode, addnodes.desc_signature):
|
||||
continue
|
||||
@ -72,6 +78,7 @@ def doctree_read(app, doctree):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.connect('doctree-read', doctree_read)
|
||||
app.add_config_value('linkcode_resolve', None, '')
|
||||
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
|
||||
|
@ -18,6 +18,14 @@ from sphinx.domains import Domain
|
||||
from sphinx.util.nodes import make_refnode, set_source_info
|
||||
from sphinx.util.compat import Directive
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Iterable, Tuple # NOQA
|
||||
from docutils.parsers.rst.states import Inliner # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.builders import Builder # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
|
||||
|
||||
class math(nodes.Inline, nodes.TextElement):
|
||||
pass
|
||||
@ -33,6 +41,7 @@ class eqref(nodes.Inline, nodes.TextElement):
|
||||
|
||||
class EqXRefRole(XRefRole):
|
||||
def result_nodes(self, document, env, node, is_ref):
|
||||
# type: (nodes.Node, BuildEnvironment, nodes.Node, bool) -> Tuple[List[nodes.Node], List[nodes.Node]] # NOQA
|
||||
node['refdomain'] = 'math'
|
||||
return [node], []
|
||||
|
||||
@ -44,22 +53,25 @@ class MathDomain(Domain):
|
||||
|
||||
initial_data = {
|
||||
'objects': {}, # labelid -> (docname, eqno)
|
||||
}
|
||||
} # type: Dict[unicode, Dict[unicode, Tuple[unicode, int]]]
|
||||
dangling_warnings = {
|
||||
'eq': 'equation not found: %(target)s',
|
||||
}
|
||||
|
||||
def clear_doc(self, docname):
|
||||
# type: (unicode) -> None
|
||||
for labelid, (doc, eqno) in list(self.data['objects'].items()):
|
||||
if doc == docname:
|
||||
del self.data['objects'][labelid]
|
||||
|
||||
def merge_domaindata(self, docnames, otherdata):
|
||||
# type: (Iterable[unicode], Dict) -> None
|
||||
for labelid, (doc, eqno) in otherdata['objects'].items():
|
||||
if doc in docnames:
|
||||
self.data['objects'][labelid] = doc
|
||||
|
||||
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
|
||||
# type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA
|
||||
assert typ == 'eq'
|
||||
docname, number = self.data['objects'].get(target, (None, None))
|
||||
if docname:
|
||||
@ -76,6 +88,7 @@ class MathDomain(Domain):
|
||||
return None
|
||||
|
||||
def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
|
||||
# type: (BuildEnvironment, unicode, Builder, unicode, nodes.Node, nodes.Node) -> List[nodes.Node] # NOQA
|
||||
refnode = self.resolve_xref(env, fromdocname, builder, 'eq', target, node, contnode)
|
||||
if refnode is None:
|
||||
return []
|
||||
@ -83,9 +96,11 @@ class MathDomain(Domain):
|
||||
return [refnode]
|
||||
|
||||
def get_objects(self):
|
||||
# type: () -> List
|
||||
return []
|
||||
|
||||
def add_equation(self, env, docname, labelid):
|
||||
# type: (BuildEnvironment, unicode, unicode) -> int
|
||||
equations = self.data['objects']
|
||||
if labelid in equations:
|
||||
path = env.doc2path(equations[labelid][0])
|
||||
@ -97,12 +112,15 @@ class MathDomain(Domain):
|
||||
return eqno
|
||||
|
||||
def get_next_equation_number(self, docname):
|
||||
# type: (unicode) -> int
|
||||
targets = [eq for eq in self.data['objects'].values() if eq[0] == docname]
|
||||
return len(targets) + 1
|
||||
|
||||
|
||||
def wrap_displaymath(math, label, numbering):
|
||||
# type: (unicode, unicode, bool) -> unicode
|
||||
def is_equation(part):
|
||||
# type: (unicode) -> unicode
|
||||
return part.strip()
|
||||
|
||||
if label is None:
|
||||
@ -137,11 +155,13 @@ def wrap_displaymath(math, label, numbering):
|
||||
|
||||
|
||||
def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
# type: (unicode, unicode, unicode, int, Inliner, Dict, List[unicode]) -> Tuple[List[nodes.Node], List[nodes.Node]] # NOQA
|
||||
latex = utils.unescape(text, restore_backslashes=True)
|
||||
return [math(latex=latex)], []
|
||||
|
||||
|
||||
def is_in_section_title(node):
|
||||
# type: (nodes.Node) -> bool
|
||||
"""Determine whether the node is in a section title"""
|
||||
from sphinx.util.nodes import traverse_parent
|
||||
|
||||
@ -165,6 +185,7 @@ class MathDirective(Directive):
|
||||
}
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
latex = '\n'.join(self.content)
|
||||
if self.arguments and self.arguments[0]:
|
||||
latex = self.arguments[0] + '\n\n' + latex
|
||||
@ -186,6 +207,7 @@ class MathDirective(Directive):
|
||||
return ret
|
||||
|
||||
def add_target(self, ret):
|
||||
# type: (List[nodes.Node]) -> None
|
||||
node = ret[0]
|
||||
env = self.state.document.settings.env
|
||||
|
||||
@ -213,6 +235,7 @@ class MathDirective(Directive):
|
||||
|
||||
|
||||
def latex_visit_math(self, node):
|
||||
# type: (nodes.NodeVisitor, math) -> None
|
||||
if is_in_section_title(node):
|
||||
protect = r'\protect'
|
||||
else:
|
||||
@ -223,6 +246,7 @@ def latex_visit_math(self, node):
|
||||
|
||||
|
||||
def latex_visit_displaymath(self, node):
|
||||
# type: (nodes.NodeVisitor, displaymath) -> None
|
||||
if not node['label']:
|
||||
label = None
|
||||
else:
|
||||
@ -239,17 +263,20 @@ def latex_visit_displaymath(self, node):
|
||||
|
||||
|
||||
def latex_visit_eqref(self, node):
|
||||
# type: (nodes.NodeVisitor, eqref) -> None
|
||||
label = "equation:%s:%s" % (node['docname'], node['target'])
|
||||
self.body.append('\\eqref{%s}' % label)
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def text_visit_math(self, node):
|
||||
# type: (nodes.NodeVisitor, math) -> None
|
||||
self.add_text(node['latex'])
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def text_visit_displaymath(self, node):
|
||||
# type: (nodes.NodeVisitor, displaymath) -> None
|
||||
self.new_state()
|
||||
self.add_text(node['latex'])
|
||||
self.end_state()
|
||||
@ -257,24 +284,29 @@ def text_visit_displaymath(self, node):
|
||||
|
||||
|
||||
def man_visit_math(self, node):
|
||||
# type: (nodes.NodeVisitor, math) -> None
|
||||
self.body.append(node['latex'])
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def man_visit_displaymath(self, node):
|
||||
# type: (nodes.NodeVisitor, displaymath) -> None
|
||||
self.visit_centered(node)
|
||||
|
||||
|
||||
def man_depart_displaymath(self, node):
|
||||
# type: (nodes.NodeVisitor, displaymath) -> None
|
||||
self.depart_centered(node)
|
||||
|
||||
|
||||
def texinfo_visit_math(self, node):
|
||||
# type: (nodes.NodeVisitor, math) -> None
|
||||
self.body.append('@math{' + self.escape_arg(node['latex']) + '}')
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def texinfo_visit_displaymath(self, node):
|
||||
# type: (nodes.NodeVisitor, displaymath) -> None
|
||||
if node.get('label'):
|
||||
self.add_anchor(node['label'], node)
|
||||
self.body.append('\n\n@example\n%s\n@end example\n\n' %
|
||||
@ -282,10 +314,12 @@ def texinfo_visit_displaymath(self, node):
|
||||
|
||||
|
||||
def texinfo_depart_displaymath(self, node):
|
||||
# type: (nodes.NodeVisitor, displaymath) -> None
|
||||
pass
|
||||
|
||||
|
||||
def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
|
||||
# type: (Sphinx, Tuple[Callable, Any], Tuple[Callable, Any]) -> None
|
||||
app.add_config_value('math_number_all', False, 'env')
|
||||
app.add_domain(MathDomain)
|
||||
app.add_node(math, override=True,
|
||||
|
@ -14,8 +14,13 @@ import sys
|
||||
from six import PY2, iteritems
|
||||
|
||||
import sphinx
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.ext.napoleon.docstring import GoogleDocstring, NumpyDocstring
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any # NOQA
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""Sphinx napoleon extension settings in `conf.py`.
|
||||
@ -254,6 +259,7 @@ class Config(object):
|
||||
}
|
||||
|
||||
def __init__(self, **settings):
|
||||
# type: (Any) -> None
|
||||
for name, (default, rebuild) in iteritems(self._config_values):
|
||||
setattr(self, name, default)
|
||||
for name, value in iteritems(settings):
|
||||
@ -261,6 +267,7 @@ class Config(object):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
"""Sphinx extension setup function.
|
||||
|
||||
When the extension is loaded, Sphinx imports this module and executes
|
||||
@ -282,9 +289,9 @@ def setup(app):
|
||||
`The Extension API <http://sphinx-doc.org/extdev/appapi.html>`_
|
||||
|
||||
"""
|
||||
from sphinx.application import Sphinx
|
||||
if not isinstance(app, Sphinx):
|
||||
return # probably called by tests
|
||||
return # type: ignore
|
||||
# probably called by tests
|
||||
|
||||
_patch_python_domain()
|
||||
|
||||
@ -297,13 +304,14 @@ def setup(app):
|
||||
|
||||
|
||||
def _patch_python_domain():
|
||||
# type: () -> None
|
||||
try:
|
||||
from sphinx.domains.python import PyTypedField
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
import sphinx.domains.python
|
||||
import sphinx.locale
|
||||
import sphinx.locale # type: ignore
|
||||
l_ = sphinx.locale.lazy_gettext
|
||||
for doc_field in sphinx.domains.python.PyObject.doc_field_types:
|
||||
if doc_field.name == 'parameter':
|
||||
@ -317,6 +325,7 @@ def _patch_python_domain():
|
||||
|
||||
|
||||
def _process_docstring(app, what, name, obj, options, lines):
|
||||
# type: (Sphinx, unicode, unicode, Any, Any, List[unicode]) -> None
|
||||
"""Process the docstring for a given python object.
|
||||
|
||||
Called when autodoc has read and processed a docstring. `lines` is a list
|
||||
@ -353,6 +362,7 @@ def _process_docstring(app, what, name, obj, options, lines):
|
||||
|
||||
"""
|
||||
result_lines = lines
|
||||
docstring = None # type: GoogleDocstring
|
||||
if app.config.napoleon_numpy_docstring:
|
||||
docstring = NumpyDocstring(result_lines, app.config, app, what, name,
|
||||
obj, options)
|
||||
@ -365,6 +375,7 @@ def _process_docstring(app, what, name, obj, options, lines):
|
||||
|
||||
|
||||
def _skip_member(app, what, name, obj, skip, options):
|
||||
# type: (Sphinx, unicode, unicode, Any, bool, Any) -> bool
|
||||
"""Determine if private and special class members are included in docs.
|
||||
|
||||
The following settings in conf.py determine if private and special class
|
||||
|
@ -21,6 +21,12 @@ from six.moves import range
|
||||
from sphinx.ext.napoleon.iterators import modify_iter
|
||||
from sphinx.util.pycompat import UnicodeMixin
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Tuple, Union # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.config import Config as SphinxConfig # NOQA
|
||||
|
||||
|
||||
_directive_regex = re.compile(r'\.\. \S+::')
|
||||
_google_section_regex = re.compile(r'^(\s|\w)+:\s*$')
|
||||
@ -99,19 +105,20 @@ class GoogleDocstring(UnicodeMixin):
|
||||
"""
|
||||
def __init__(self, docstring, config=None, app=None, what='', name='',
|
||||
obj=None, options=None):
|
||||
# type: (Union[unicode, List[unicode]], SphinxConfig, Sphinx, unicode, unicode, Any, Any) -> None # NOQA
|
||||
self._config = config
|
||||
self._app = app
|
||||
|
||||
if not self._config:
|
||||
from sphinx.ext.napoleon import Config
|
||||
self._config = self._app and self._app.config or Config()
|
||||
self._config = self._app and self._app.config or Config() # type: ignore
|
||||
|
||||
if not what:
|
||||
if inspect.isclass(obj):
|
||||
what = 'class'
|
||||
elif inspect.ismodule(obj):
|
||||
what = 'module'
|
||||
elif isinstance(obj, collections.Callable):
|
||||
elif isinstance(obj, collections.Callable): # type: ignore
|
||||
what = 'function'
|
||||
else:
|
||||
what = 'object'
|
||||
@ -121,14 +128,14 @@ class GoogleDocstring(UnicodeMixin):
|
||||
self._obj = obj
|
||||
self._opt = options
|
||||
if isinstance(docstring, string_types):
|
||||
docstring = docstring.splitlines()
|
||||
docstring = docstring.splitlines() # type: ignore
|
||||
self._lines = docstring
|
||||
self._line_iter = modify_iter(docstring, modifier=lambda s: s.rstrip())
|
||||
self._parsed_lines = []
|
||||
self._parsed_lines = [] # type: List[unicode]
|
||||
self._is_in_section = False
|
||||
self._section_indent = 0
|
||||
if not hasattr(self, '_directive_sections'):
|
||||
self._directive_sections = []
|
||||
self._directive_sections = [] # type: List[unicode]
|
||||
if not hasattr(self, '_sections'):
|
||||
self._sections = {
|
||||
'args': self._parse_parameters_section,
|
||||
@ -154,10 +161,11 @@ class GoogleDocstring(UnicodeMixin):
|
||||
'warns': self._parse_warns_section,
|
||||
'yield': self._parse_yields_section,
|
||||
'yields': self._parse_yields_section,
|
||||
}
|
||||
} # type: Dict[unicode, Callable]
|
||||
self._parse()
|
||||
|
||||
def __unicode__(self):
|
||||
# type: () -> unicode
|
||||
"""Return the parsed docstring in reStructuredText format.
|
||||
|
||||
Returns
|
||||
@ -169,6 +177,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return u('\n').join(self.lines())
|
||||
|
||||
def lines(self):
|
||||
# type: () -> List[unicode]
|
||||
"""Return the parsed lines of the docstring in reStructuredText format.
|
||||
|
||||
Returns
|
||||
@ -180,38 +189,42 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return self._parsed_lines
|
||||
|
||||
def _consume_indented_block(self, indent=1):
|
||||
# type: (int) -> List[unicode]
|
||||
lines = []
|
||||
line = self._line_iter.peek()
|
||||
while(not self._is_section_break() and
|
||||
(not line or self._is_indented(line, indent))):
|
||||
lines.append(next(self._line_iter))
|
||||
lines.append(next(self._line_iter)) # type: ignore
|
||||
line = self._line_iter.peek()
|
||||
return lines
|
||||
|
||||
def _consume_contiguous(self):
|
||||
# type: () -> List[unicode]
|
||||
lines = []
|
||||
while (self._line_iter.has_next() and
|
||||
self._line_iter.peek() and
|
||||
not self._is_section_header()):
|
||||
lines.append(next(self._line_iter))
|
||||
lines.append(next(self._line_iter)) # type: ignore
|
||||
return lines
|
||||
|
||||
def _consume_empty(self):
|
||||
# type: () -> List[unicode]
|
||||
lines = []
|
||||
line = self._line_iter.peek()
|
||||
while self._line_iter.has_next() and not line:
|
||||
lines.append(next(self._line_iter))
|
||||
lines.append(next(self._line_iter)) # type: ignore
|
||||
line = self._line_iter.peek()
|
||||
return lines
|
||||
|
||||
def _consume_field(self, parse_type=True, prefer_type=False):
|
||||
line = next(self._line_iter)
|
||||
# type: (bool, bool) -> Tuple[unicode, unicode, List[unicode]]
|
||||
line = next(self._line_iter) # type: ignore
|
||||
|
||||
before, colon, after = self._partition_field_on_colon(line)
|
||||
_name, _type, _desc = before, '', after
|
||||
_name, _type, _desc = before, '', after # type: unicode, unicode, unicode
|
||||
|
||||
if parse_type:
|
||||
match = _google_typed_arg_regex.match(before)
|
||||
match = _google_typed_arg_regex.match(before) # type: ignore
|
||||
if match:
|
||||
_name = match.group(1)
|
||||
_type = match.group(2)
|
||||
@ -221,11 +234,12 @@ class GoogleDocstring(UnicodeMixin):
|
||||
if prefer_type and not _type:
|
||||
_type, _name = _name, _type
|
||||
indent = self._get_indent(line) + 1
|
||||
_desc = [_desc] + self._dedent(self._consume_indented_block(indent))
|
||||
_desc = [_desc] + self._dedent(self._consume_indented_block(indent)) # type: ignore
|
||||
_desc = self.__class__(_desc, self._config).lines()
|
||||
return _name, _type, _desc
|
||||
return _name, _type, _desc # type: ignore
|
||||
|
||||
def _consume_fields(self, parse_type=True, prefer_type=False):
|
||||
# type: (bool, bool) -> List[Tuple[unicode, unicode, List[unicode]]]
|
||||
self._consume_empty()
|
||||
fields = []
|
||||
while not self._is_section_break():
|
||||
@ -235,19 +249,21 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return fields
|
||||
|
||||
def _consume_inline_attribute(self):
|
||||
line = next(self._line_iter)
|
||||
# type: () -> Tuple[unicode, List[unicode]]
|
||||
line = next(self._line_iter) # type: ignore
|
||||
_type, colon, _desc = self._partition_field_on_colon(line)
|
||||
if not colon:
|
||||
_type, _desc = _desc, _type
|
||||
_desc = [_desc] + self._dedent(self._consume_to_end())
|
||||
_desc = [_desc] + self._dedent(self._consume_to_end()) # type: ignore
|
||||
_desc = self.__class__(_desc, self._config).lines()
|
||||
return _type, _desc
|
||||
return _type, _desc # type: ignore
|
||||
|
||||
def _consume_returns_section(self):
|
||||
# type: () -> List[Tuple[unicode, unicode, List[unicode]]]
|
||||
lines = self._dedent(self._consume_to_next_section())
|
||||
if lines:
|
||||
before, colon, after = self._partition_field_on_colon(lines[0])
|
||||
_name, _type, _desc = '', '', lines
|
||||
_name, _type, _desc = '', '', lines # type: unicode, unicode, List[unicode]
|
||||
|
||||
if colon:
|
||||
if after:
|
||||
@ -263,30 +279,35 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return []
|
||||
|
||||
def _consume_usage_section(self):
|
||||
# type: () -> List[unicode]
|
||||
lines = self._dedent(self._consume_to_next_section())
|
||||
return lines
|
||||
|
||||
def _consume_section_header(self):
|
||||
section = next(self._line_iter)
|
||||
# type: () -> unicode
|
||||
section = next(self._line_iter) # type: ignore
|
||||
stripped_section = section.strip(':')
|
||||
if stripped_section.lower() in self._sections:
|
||||
section = stripped_section
|
||||
return section
|
||||
|
||||
def _consume_to_end(self):
|
||||
# type: () -> List[unicode]
|
||||
lines = []
|
||||
while self._line_iter.has_next():
|
||||
lines.append(next(self._line_iter))
|
||||
lines.append(next(self._line_iter)) # type: ignore
|
||||
return lines
|
||||
|
||||
def _consume_to_next_section(self):
|
||||
# type: () -> List[unicode]
|
||||
self._consume_empty()
|
||||
lines = []
|
||||
while not self._is_section_break():
|
||||
lines.append(next(self._line_iter))
|
||||
lines.append(next(self._line_iter)) # type: ignore
|
||||
return lines + self._consume_empty()
|
||||
|
||||
def _dedent(self, lines, full=False):
|
||||
# type: (List[unicode], bool) -> List[unicode]
|
||||
if full:
|
||||
return [line.lstrip() for line in lines]
|
||||
else:
|
||||
@ -294,6 +315,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return [line[min_indent:] for line in lines]
|
||||
|
||||
def _escape_args_and_kwargs(self, name):
|
||||
# type: (unicode) -> unicode
|
||||
if name[:2] == '**':
|
||||
return r'\*\*' + name[2:]
|
||||
elif name[:1] == '*':
|
||||
@ -302,29 +324,32 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return name
|
||||
|
||||
def _fix_field_desc(self, desc):
|
||||
# type: (List[unicode]) -> List[unicode]
|
||||
if self._is_list(desc):
|
||||
desc = [''] + desc
|
||||
desc = [''] + desc # type: ignore
|
||||
elif desc[0].endswith('::'):
|
||||
desc_block = desc[1:]
|
||||
indent = self._get_indent(desc[0])
|
||||
block_indent = self._get_initial_indent(desc_block)
|
||||
if block_indent > indent:
|
||||
desc = [''] + desc
|
||||
desc = [''] + desc # type: ignore
|
||||
else:
|
||||
desc = ['', desc[0]] + self._indent(desc_block, 4)
|
||||
return desc
|
||||
|
||||
def _format_admonition(self, admonition, lines):
|
||||
# type: (unicode, List[unicode]) -> List[unicode]
|
||||
lines = self._strip_empty(lines)
|
||||
if len(lines) == 1:
|
||||
return ['.. %s:: %s' % (admonition, lines[0].strip()), '']
|
||||
elif lines:
|
||||
lines = self._indent(self._dedent(lines), 3)
|
||||
return ['.. %s::' % admonition, ''] + lines + ['']
|
||||
return ['.. %s::' % admonition, ''] + lines + [''] # type: ignore
|
||||
else:
|
||||
return ['.. %s::' % admonition, '']
|
||||
|
||||
def _format_block(self, prefix, lines, padding=None):
|
||||
# type: (unicode, List[unicode], unicode) -> List[unicode]
|
||||
if lines:
|
||||
if padding is None:
|
||||
padding = ' ' * len(prefix)
|
||||
@ -342,6 +367,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
|
||||
def _format_docutils_params(self, fields, field_role='param',
|
||||
type_role='type'):
|
||||
# type: (List[Tuple[unicode, unicode, List[unicode]]], unicode, unicode) -> List[unicode] # NOQA
|
||||
lines = []
|
||||
for _name, _type, _desc in fields:
|
||||
_desc = self._strip_empty(_desc)
|
||||
@ -357,13 +383,14 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return lines + ['']
|
||||
|
||||
def _format_field(self, _name, _type, _desc):
|
||||
# type: (unicode, unicode, List[unicode]) -> List[unicode]
|
||||
_desc = self._strip_empty(_desc)
|
||||
has_desc = any(_desc)
|
||||
separator = has_desc and ' -- ' or ''
|
||||
if _name:
|
||||
if _type:
|
||||
if '`' in _type:
|
||||
field = '**%s** (%s)%s' % (_name, _type, separator)
|
||||
field = '**%s** (%s)%s' % (_name, _type, separator) # type: unicode
|
||||
else:
|
||||
field = '**%s** (*%s*)%s' % (_name, _type, separator)
|
||||
else:
|
||||
@ -386,10 +413,11 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return [field]
|
||||
|
||||
def _format_fields(self, field_type, fields):
|
||||
# type: (unicode, List[Tuple[unicode, unicode, List[unicode]]]) -> List[unicode]
|
||||
field_type = ':%s:' % field_type.strip()
|
||||
padding = ' ' * len(field_type)
|
||||
multi = len(fields) > 1
|
||||
lines = []
|
||||
lines = [] # type: List[unicode]
|
||||
for _name, _type, _desc in fields:
|
||||
field = self._format_field(_name, _type, _desc)
|
||||
if multi:
|
||||
@ -404,6 +432,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return lines
|
||||
|
||||
def _get_current_indent(self, peek_ahead=0):
|
||||
# type: (int) -> int
|
||||
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
|
||||
while line != self._line_iter.sentinel:
|
||||
if line:
|
||||
@ -413,18 +442,21 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return 0
|
||||
|
||||
def _get_indent(self, line):
|
||||
# type: (unicode) -> int
|
||||
for i, s in enumerate(line):
|
||||
if not s.isspace():
|
||||
return i
|
||||
return len(line)
|
||||
|
||||
def _get_initial_indent(self, lines):
|
||||
# type: (List[unicode]) -> int
|
||||
for line in lines:
|
||||
if line:
|
||||
return self._get_indent(line)
|
||||
return 0
|
||||
|
||||
def _get_min_indent(self, lines):
|
||||
# type: (List[unicode]) -> int
|
||||
min_indent = None
|
||||
for line in lines:
|
||||
if line:
|
||||
@ -436,9 +468,11 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return min_indent or 0
|
||||
|
||||
def _indent(self, lines, n=4):
|
||||
# type: (List[unicode], int) -> List[unicode]
|
||||
return [(' ' * n) + line for line in lines]
|
||||
|
||||
def _is_indented(self, line, indent=1):
|
||||
# type: (unicode, int) -> bool
|
||||
for i, s in enumerate(line):
|
||||
if i >= indent:
|
||||
return True
|
||||
@ -447,11 +481,12 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return False
|
||||
|
||||
def _is_list(self, lines):
|
||||
# type: (List[unicode]) -> bool
|
||||
if not lines:
|
||||
return False
|
||||
if _bullet_list_regex.match(lines[0]):
|
||||
if _bullet_list_regex.match(lines[0]): # type: ignore
|
||||
return True
|
||||
if _enumerated_list_regex.match(lines[0]):
|
||||
if _enumerated_list_regex.match(lines[0]): # type: ignore
|
||||
return True
|
||||
if len(lines) < 2 or lines[0].endswith('::'):
|
||||
return False
|
||||
@ -464,6 +499,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return next_indent > indent
|
||||
|
||||
def _is_section_header(self):
|
||||
# type: () -> bool
|
||||
section = self._line_iter.peek().lower()
|
||||
match = _google_section_regex.match(section)
|
||||
if match and section.strip(':') in self._sections:
|
||||
@ -478,6 +514,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return False
|
||||
|
||||
def _is_section_break(self):
|
||||
# type: () -> bool
|
||||
line = self._line_iter.peek()
|
||||
return (not self._line_iter.has_next() or
|
||||
self._is_section_header() or
|
||||
@ -486,6 +523,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
not self._is_indented(line, self._section_indent)))
|
||||
|
||||
def _parse(self):
|
||||
# type: () -> None
|
||||
self._parsed_lines = self._consume_empty()
|
||||
|
||||
if self._name and (self._what == 'attribute' or self._what == 'data'):
|
||||
@ -498,7 +536,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
section = self._consume_section_header()
|
||||
self._is_in_section = True
|
||||
self._section_indent = self._get_current_indent()
|
||||
if _directive_regex.match(section):
|
||||
if _directive_regex.match(section): # type: ignore
|
||||
lines = [section] + self._consume_to_next_section()
|
||||
else:
|
||||
lines = self._sections[section.lower()](section)
|
||||
@ -513,42 +551,47 @@ class GoogleDocstring(UnicodeMixin):
|
||||
self._parsed_lines.extend(lines)
|
||||
|
||||
def _parse_attribute_docstring(self):
|
||||
# type: () -> List[unicode]
|
||||
_type, _desc = self._consume_inline_attribute()
|
||||
return self._format_field('', _type, _desc)
|
||||
|
||||
def _parse_attributes_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
lines = []
|
||||
for _name, _type, _desc in self._consume_fields():
|
||||
if self._config.napoleon_use_ivar:
|
||||
field = ':ivar %s: ' % _name
|
||||
field = ':ivar %s: ' % _name # type: unicode
|
||||
lines.extend(self._format_block(field, _desc))
|
||||
if _type:
|
||||
lines.append(':vartype %s: %s' % (_name, _type))
|
||||
else:
|
||||
lines.extend(['.. attribute:: ' + _name, ''])
|
||||
field = self._format_field('', _type, _desc)
|
||||
lines.extend(self._indent(field, 3))
|
||||
field = self._format_field('', _type, _desc) # type: ignore
|
||||
lines.extend(self._indent(field, 3)) # type: ignore
|
||||
lines.append('')
|
||||
if self._config.napoleon_use_ivar:
|
||||
lines.append('')
|
||||
return lines
|
||||
|
||||
def _parse_examples_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
use_admonition = self._config.napoleon_use_admonition_for_examples
|
||||
return self._parse_generic_section(section, use_admonition)
|
||||
|
||||
def _parse_usage_section(self, section):
|
||||
header = ['.. rubric:: Usage:', '']
|
||||
block = ['.. code-block:: python', '']
|
||||
# type: (unicode) -> List[unicode]
|
||||
header = ['.. rubric:: Usage:', ''] # type: List[unicode]
|
||||
block = ['.. code-block:: python', ''] # type: List[unicode]
|
||||
lines = self._consume_usage_section()
|
||||
lines = self._indent(lines, 3)
|
||||
return header + block + lines + ['']
|
||||
|
||||
def _parse_generic_section(self, section, use_admonition):
|
||||
# type: (unicode, bool) -> List[unicode]
|
||||
lines = self._strip_empty(self._consume_to_next_section())
|
||||
lines = self._dedent(lines)
|
||||
if use_admonition:
|
||||
header = '.. admonition:: %s' % section
|
||||
header = '.. admonition:: %s' % section # type: unicode
|
||||
lines = self._indent(lines, 3)
|
||||
else:
|
||||
header = '.. rubric:: %s' % section
|
||||
@ -558,6 +601,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return [header, '']
|
||||
|
||||
def _parse_keyword_arguments_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
fields = self._consume_fields()
|
||||
if self._config.napoleon_use_keyword:
|
||||
return self._format_docutils_params(
|
||||
@ -568,26 +612,31 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return self._format_fields('Keyword Arguments', fields)
|
||||
|
||||
def _parse_methods_section(self, section):
|
||||
lines = []
|
||||
# type: (unicode) -> List[unicode]
|
||||
lines = [] # type: List[unicode]
|
||||
for _name, _, _desc in self._consume_fields(parse_type=False):
|
||||
lines.append('.. method:: %s' % _name)
|
||||
if _desc:
|
||||
lines.extend([''] + self._indent(_desc, 3))
|
||||
lines.extend([''] + self._indent(_desc, 3)) # type: ignore
|
||||
lines.append('')
|
||||
return lines
|
||||
|
||||
def _parse_note_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
lines = self._consume_to_next_section()
|
||||
return self._format_admonition('note', lines)
|
||||
|
||||
def _parse_notes_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
use_admonition = self._config.napoleon_use_admonition_for_notes
|
||||
return self._parse_generic_section('Notes', use_admonition)
|
||||
|
||||
def _parse_other_parameters_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
return self._format_fields('Other Parameters', self._consume_fields())
|
||||
|
||||
def _parse_parameters_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
fields = self._consume_fields()
|
||||
if self._config.napoleon_use_param:
|
||||
return self._format_docutils_params(fields)
|
||||
@ -595,11 +644,12 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return self._format_fields('Parameters', fields)
|
||||
|
||||
def _parse_raises_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
fields = self._consume_fields(parse_type=False, prefer_type=True)
|
||||
field_type = ':raises:'
|
||||
padding = ' ' * len(field_type)
|
||||
multi = len(fields) > 1
|
||||
lines = []
|
||||
lines = [] # type: List[unicode]
|
||||
for _, _type, _desc in fields:
|
||||
_desc = self._strip_empty(_desc)
|
||||
has_desc = any(_desc)
|
||||
@ -633,10 +683,12 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return lines
|
||||
|
||||
def _parse_references_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
use_admonition = self._config.napoleon_use_admonition_for_references
|
||||
return self._parse_generic_section('References', use_admonition)
|
||||
|
||||
def _parse_returns_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
fields = self._consume_returns_section()
|
||||
multi = len(fields) > 1
|
||||
if multi:
|
||||
@ -644,7 +696,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
else:
|
||||
use_rtype = self._config.napoleon_use_rtype
|
||||
|
||||
lines = []
|
||||
lines = [] # type: List[unicode]
|
||||
for _name, _type, _desc in fields:
|
||||
if use_rtype:
|
||||
field = self._format_field(_name, '', _desc)
|
||||
@ -665,30 +717,36 @@ class GoogleDocstring(UnicodeMixin):
|
||||
return lines
|
||||
|
||||
def _parse_see_also_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
lines = self._consume_to_next_section()
|
||||
return self._format_admonition('seealso', lines)
|
||||
|
||||
def _parse_todo_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
lines = self._consume_to_next_section()
|
||||
return self._format_admonition('todo', lines)
|
||||
|
||||
def _parse_warning_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
lines = self._consume_to_next_section()
|
||||
return self._format_admonition('warning', lines)
|
||||
|
||||
def _parse_warns_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
return self._format_fields('Warns', self._consume_fields())
|
||||
|
||||
def _parse_yields_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
fields = self._consume_returns_section()
|
||||
return self._format_fields('Yields', fields)
|
||||
|
||||
def _partition_field_on_colon(self, line):
|
||||
# type: (unicode) -> Tuple[unicode, unicode, unicode]
|
||||
before_colon = []
|
||||
after_colon = []
|
||||
colon = ''
|
||||
found_colon = False
|
||||
for i, source in enumerate(_xref_regex.split(line)):
|
||||
for i, source in enumerate(_xref_regex.split(line)): # type: ignore
|
||||
if found_colon:
|
||||
after_colon.append(source)
|
||||
else:
|
||||
@ -706,6 +764,7 @@ class GoogleDocstring(UnicodeMixin):
|
||||
"".join(after_colon).strip())
|
||||
|
||||
def _strip_empty(self, lines):
|
||||
# type: (List[unicode]) -> List[unicode]
|
||||
if lines:
|
||||
start = -1
|
||||
for i, line in enumerate(lines):
|
||||
@ -820,12 +879,14 @@ class NumpyDocstring(GoogleDocstring):
|
||||
"""
|
||||
def __init__(self, docstring, config=None, app=None, what='', name='',
|
||||
obj=None, options=None):
|
||||
# type: (Union[unicode, List[unicode]], SphinxConfig, Sphinx, unicode, unicode, Any, Any) -> None # NOQA
|
||||
self._directive_sections = ['.. index::']
|
||||
super(NumpyDocstring, self).__init__(docstring, config, app, what,
|
||||
name, obj, options)
|
||||
|
||||
def _consume_field(self, parse_type=True, prefer_type=False):
|
||||
line = next(self._line_iter)
|
||||
# type: (bool, bool) -> Tuple[unicode, unicode, List[unicode]]
|
||||
line = next(self._line_iter) # type: ignore
|
||||
if parse_type:
|
||||
_name, _, _type = self._partition_field_on_colon(line)
|
||||
else:
|
||||
@ -841,16 +902,19 @@ class NumpyDocstring(GoogleDocstring):
|
||||
return _name, _type, _desc
|
||||
|
||||
def _consume_returns_section(self):
|
||||
# type: () -> List[Tuple[unicode, unicode, List[unicode]]]
|
||||
return self._consume_fields(prefer_type=True)
|
||||
|
||||
def _consume_section_header(self):
|
||||
section = next(self._line_iter)
|
||||
# type: () -> unicode
|
||||
section = next(self._line_iter) # type: ignore
|
||||
if not _directive_regex.match(section):
|
||||
# Consume the header underline
|
||||
next(self._line_iter)
|
||||
next(self._line_iter) # type: ignore
|
||||
return section
|
||||
|
||||
def _is_section_break(self):
|
||||
# type: () -> bool
|
||||
line1, line2 = self._line_iter.peek(2)
|
||||
return (not self._line_iter.has_next() or
|
||||
self._is_section_header() or
|
||||
@ -860,10 +924,11 @@ class NumpyDocstring(GoogleDocstring):
|
||||
not self._is_indented(line1, self._section_indent)))
|
||||
|
||||
def _is_section_header(self):
|
||||
# type: () -> bool
|
||||
section, underline = self._line_iter.peek(2)
|
||||
section = section.lower()
|
||||
if section in self._sections and isinstance(underline, string_types):
|
||||
return bool(_numpy_section_regex.match(underline))
|
||||
return bool(_numpy_section_regex.match(underline)) # type: ignore
|
||||
elif self._directive_sections:
|
||||
if _directive_regex.match(section):
|
||||
for directive_section in self._directive_sections:
|
||||
@ -875,6 +940,7 @@ class NumpyDocstring(GoogleDocstring):
|
||||
r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X)
|
||||
|
||||
def _parse_see_also_section(self, section):
|
||||
# type: (unicode) -> List[unicode]
|
||||
lines = self._consume_to_next_section()
|
||||
try:
|
||||
return self._parse_numpydoc_see_also_section(lines)
|
||||
@ -882,6 +948,7 @@ class NumpyDocstring(GoogleDocstring):
|
||||
return self._format_admonition('seealso', lines)
|
||||
|
||||
def _parse_numpydoc_see_also_section(self, content):
|
||||
# type: (List[unicode]) -> List[unicode]
|
||||
"""
|
||||
Derived from the NumpyDoc implementation of _parse_see_also.
|
||||
|
||||
@ -914,13 +981,13 @@ class NumpyDocstring(GoogleDocstring):
|
||||
del rest[:]
|
||||
|
||||
current_func = None
|
||||
rest = []
|
||||
rest = [] # type: List[unicode]
|
||||
|
||||
for line in content:
|
||||
if not line.strip():
|
||||
continue
|
||||
|
||||
m = self._name_rgx.match(line)
|
||||
m = self._name_rgx.match(line) # type: ignore
|
||||
if m and line[m.end():].strip().startswith(':'):
|
||||
push_item(current_func, rest)
|
||||
current_func, line = line[:m.end()], line[m.end():]
|
||||
@ -960,12 +1027,12 @@ class NumpyDocstring(GoogleDocstring):
|
||||
'const': 'const',
|
||||
'attribute': 'attr',
|
||||
'attr': 'attr'
|
||||
}
|
||||
} # type: Dict[unicode, unicode]
|
||||
if self._what is None:
|
||||
func_role = 'obj'
|
||||
func_role = 'obj' # type: unicode
|
||||
else:
|
||||
func_role = roles.get(self._what, '')
|
||||
lines = []
|
||||
lines = [] # type: List[unicode]
|
||||
last_had_desc = True
|
||||
for func, desc, role in items:
|
||||
if role:
|
||||
|
@ -13,6 +13,10 @@
|
||||
|
||||
import collections
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Iterable # NOQA
|
||||
|
||||
|
||||
class peek_iter(object):
|
||||
"""An iterator object that supports peeking ahead.
|
||||
@ -48,34 +52,39 @@ class peek_iter(object):
|
||||
|
||||
"""
|
||||
def __init__(self, *args):
|
||||
# type: (Any) -> None
|
||||
"""__init__(o, sentinel=None)"""
|
||||
self._iterable = iter(*args)
|
||||
self._cache = collections.deque()
|
||||
self._iterable = iter(*args) # type: Iterable
|
||||
self._cache = collections.deque() # type: collections.deque
|
||||
if len(args) == 2:
|
||||
self.sentinel = args[1]
|
||||
else:
|
||||
self.sentinel = object()
|
||||
|
||||
def __iter__(self):
|
||||
# type: () -> peek_iter
|
||||
return self
|
||||
|
||||
def __next__(self, n=None):
|
||||
# type: (int) -> Any
|
||||
# note: prevent 2to3 to transform self.next() in next(self) which
|
||||
# causes an infinite loop !
|
||||
return getattr(self, 'next')(n)
|
||||
|
||||
def _fillcache(self, n):
|
||||
# type: (int) -> None
|
||||
"""Cache `n` items. If `n` is 0 or None, then 1 item is cached."""
|
||||
if not n:
|
||||
n = 1
|
||||
try:
|
||||
while len(self._cache) < n:
|
||||
self._cache.append(next(self._iterable))
|
||||
self._cache.append(next(self._iterable)) # type: ignore
|
||||
except StopIteration:
|
||||
while len(self._cache) < n:
|
||||
self._cache.append(self.sentinel)
|
||||
|
||||
def has_next(self):
|
||||
# type: () -> bool
|
||||
"""Determine if iterator is exhausted.
|
||||
|
||||
Returns
|
||||
@ -91,6 +100,7 @@ class peek_iter(object):
|
||||
return self.peek() != self.sentinel
|
||||
|
||||
def next(self, n=None):
|
||||
# type: (int) -> Any
|
||||
"""Get the next item or `n` items of the iterator.
|
||||
|
||||
Parameters
|
||||
@ -126,6 +136,7 @@ class peek_iter(object):
|
||||
return result
|
||||
|
||||
def peek(self, n=None):
|
||||
# type: (int) -> Any
|
||||
"""Preview the next item or `n` items of the iterator.
|
||||
|
||||
The iterator is not advanced when peek is called.
|
||||
@ -209,6 +220,7 @@ class modify_iter(peek_iter):
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
# type: (Any, Any) -> None
|
||||
"""__init__(o, sentinel=None, modifier=lambda x: x)"""
|
||||
if 'modifier' in kwargs:
|
||||
self.modifier = kwargs['modifier']
|
||||
@ -223,6 +235,7 @@ class modify_iter(peek_iter):
|
||||
super(modify_iter, self).__init__(*args)
|
||||
|
||||
def _fillcache(self, n):
|
||||
# type: (int) -> None
|
||||
"""Cache `n` modified items. If `n` is 0 or None, 1 item is cached.
|
||||
|
||||
Each item returned by the iterator is passed through the
|
||||
@ -233,7 +246,7 @@ class modify_iter(peek_iter):
|
||||
n = 1
|
||||
try:
|
||||
while len(self._cache) < n:
|
||||
self._cache.append(self.modifier(next(self._iterable)))
|
||||
self._cache.append(self.modifier(next(self._iterable))) # type: ignore
|
||||
except StopIteration:
|
||||
while len(self._cache) < n:
|
||||
self._cache.append(self.sentinel)
|
||||
|
@ -20,6 +20,7 @@ from subprocess import Popen, PIPE
|
||||
from hashlib import sha1
|
||||
|
||||
from six import text_type
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
import sphinx
|
||||
@ -29,11 +30,18 @@ from sphinx.util.osutil import ensuredir, ENOENT, cd
|
||||
from sphinx.util.pycompat import sys_encoding
|
||||
from sphinx.ext.mathbase import setup_math as mathbase_setup, wrap_displaymath
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Tuple # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.ext.mathbase import math as math_node, displaymath # NOQA
|
||||
|
||||
|
||||
class MathExtError(SphinxError):
|
||||
category = 'Math extension error'
|
||||
|
||||
def __init__(self, msg, stderr=None, stdout=None):
|
||||
# type: (unicode, unicode, unicode) -> None
|
||||
if stderr:
|
||||
msg += '\n[stderr]\n' + stderr.decode(sys_encoding, 'replace')
|
||||
if stdout:
|
||||
@ -71,6 +79,7 @@ depth_re = re.compile(br'\[\d+ depth=(-?\d+)\]')
|
||||
|
||||
|
||||
def render_math(self, math):
|
||||
# type: (nodes.NodeVisitor, unicode) -> Tuple[unicode, int]
|
||||
"""Render the LaTeX math expression *math* using latex and dvipng.
|
||||
|
||||
Return the filename relative to the built document and the "depth",
|
||||
@ -107,9 +116,8 @@ def render_math(self, math):
|
||||
else:
|
||||
tempdir = self.builder._mathpng_tempdir
|
||||
|
||||
tf = codecs.open(path.join(tempdir, 'math.tex'), 'w', 'utf-8')
|
||||
tf.write(latex)
|
||||
tf.close()
|
||||
with codecs.open(path.join(tempdir, 'math.tex'), 'w', 'utf-8') as tf: # type: ignore
|
||||
tf.write(latex)
|
||||
|
||||
# build latex command; old versions of latex don't have the
|
||||
# --output-directory option, so we have to manually chdir to the
|
||||
@ -171,23 +179,26 @@ def render_math(self, math):
|
||||
|
||||
|
||||
def cleanup_tempdir(app, exc):
|
||||
# type: (Sphinx, Exception) -> None
|
||||
if exc:
|
||||
return
|
||||
if not hasattr(app.builder, '_mathpng_tempdir'):
|
||||
return
|
||||
try:
|
||||
shutil.rmtree(app.builder._mathpng_tempdir)
|
||||
shutil.rmtree(app.builder._mathpng_tempdir) # type: ignore
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def get_tooltip(self, node):
|
||||
# type: (nodes.NodeVisitor, math_node) -> unicode
|
||||
if self.builder.config.pngmath_add_tooltips:
|
||||
return ' alt="%s"' % self.encode(node['latex']).strip()
|
||||
return ''
|
||||
|
||||
|
||||
def html_visit_math(self, node):
|
||||
# type: (nodes.NodeVisitor, math_node) -> None
|
||||
try:
|
||||
fname, depth = render_math(self, '$'+node['latex']+'$')
|
||||
except MathExtError as exc:
|
||||
@ -210,6 +221,7 @@ def html_visit_math(self, node):
|
||||
|
||||
|
||||
def html_visit_displaymath(self, node):
|
||||
# type: (nodes.NodeVisitor, displaymath) -> None
|
||||
if node['nowrap']:
|
||||
latex = node['latex']
|
||||
else:
|
||||
@ -238,6 +250,7 @@ def html_visit_displaymath(self, node):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.warn('sphinx.ext.pngmath has been deprecated. Please use sphinx.ext.imgmath instead.')
|
||||
try:
|
||||
mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None))
|
||||
|
@ -22,6 +22,12 @@ from sphinx.util.nodes import set_source_info
|
||||
from docutils.parsers.rst import Directive
|
||||
from docutils.parsers.rst.directives.admonitions import BaseAdmonition
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Iterable # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
|
||||
|
||||
class todo_node(nodes.Admonition, nodes.Element):
|
||||
pass
|
||||
@ -46,6 +52,7 @@ class Todo(BaseAdmonition):
|
||||
}
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
if not self.options.get('class'):
|
||||
self.options['class'] = ['admonition-todo']
|
||||
|
||||
@ -63,12 +70,13 @@ class Todo(BaseAdmonition):
|
||||
|
||||
|
||||
def process_todos(app, doctree):
|
||||
# type: (Sphinx, nodes.Node) -> None
|
||||
# collect all todos in the environment
|
||||
# this is not done in the directive itself because it some transformations
|
||||
# must have already been run, e.g. substitutions
|
||||
env = app.builder.env
|
||||
if not hasattr(env, 'todo_all_todos'):
|
||||
env.todo_all_todos = []
|
||||
env.todo_all_todos = [] # type: ignore
|
||||
for node in doctree.traverse(todo_node):
|
||||
app.emit('todo-defined', node)
|
||||
|
||||
@ -80,7 +88,7 @@ def process_todos(app, doctree):
|
||||
targetnode = None
|
||||
newnode = node.deepcopy()
|
||||
del newnode['ids']
|
||||
env.todo_all_todos.append({
|
||||
env.todo_all_todos.append({ # type: ignore
|
||||
'docname': env.docname,
|
||||
'source': node.source or env.doc2path(env.docname),
|
||||
'lineno': node.line,
|
||||
@ -101,15 +109,17 @@ class TodoList(Directive):
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {}
|
||||
option_spec = {} # type: Dict
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[todolist]
|
||||
# Simply insert an empty todolist node which will be replaced later
|
||||
# when process_todo_nodes is called
|
||||
return [todolist('')]
|
||||
|
||||
|
||||
def process_todo_nodes(app, doctree, fromdocname):
|
||||
# type: (Sphinx, nodes.Node, unicode) -> None
|
||||
if not app.config['todo_include_todos']:
|
||||
for node in doctree.traverse(todo_node):
|
||||
node.parent.remove(node)
|
||||
@ -119,7 +129,7 @@ def process_todo_nodes(app, doctree, fromdocname):
|
||||
env = app.builder.env
|
||||
|
||||
if not hasattr(env, 'todo_all_todos'):
|
||||
env.todo_all_todos = []
|
||||
env.todo_all_todos = [] # type: ignore
|
||||
|
||||
for node in doctree.traverse(todolist):
|
||||
if not app.config['todo_include_todos']:
|
||||
@ -128,7 +138,7 @@ def process_todo_nodes(app, doctree, fromdocname):
|
||||
|
||||
content = []
|
||||
|
||||
for todo_info in env.todo_all_todos:
|
||||
for todo_info in env.todo_all_todos: # type: ignore
|
||||
para = nodes.paragraph(classes=['todo-source'])
|
||||
if app.config['todo_link_only']:
|
||||
description = _('<<original entry>>')
|
||||
@ -168,30 +178,35 @@ def process_todo_nodes(app, doctree, fromdocname):
|
||||
|
||||
|
||||
def purge_todos(app, env, docname):
|
||||
# type: (Sphinx, BuildEnvironment, unicode) -> None
|
||||
if not hasattr(env, 'todo_all_todos'):
|
||||
return
|
||||
env.todo_all_todos = [todo for todo in env.todo_all_todos
|
||||
env.todo_all_todos = [todo for todo in env.todo_all_todos # type: ignore
|
||||
if todo['docname'] != docname]
|
||||
|
||||
|
||||
def merge_info(app, env, docnames, other):
|
||||
# type: (Sphinx, BuildEnvironment, Iterable[unicode], BuildEnvironment) -> None
|
||||
if not hasattr(other, 'todo_all_todos'):
|
||||
return
|
||||
if not hasattr(env, 'todo_all_todos'):
|
||||
env.todo_all_todos = []
|
||||
env.todo_all_todos.extend(other.todo_all_todos)
|
||||
env.todo_all_todos = [] # type: ignore
|
||||
env.todo_all_todos.extend(other.todo_all_todos) # type: ignore
|
||||
|
||||
|
||||
def visit_todo_node(self, node):
|
||||
# type: (nodes.NodeVisitor, todo_node) -> None
|
||||
self.visit_admonition(node)
|
||||
# self.visit_admonition(node, 'todo')
|
||||
|
||||
|
||||
def depart_todo_node(self, node):
|
||||
# type: (nodes.NodeVisitor, todo_node) -> None
|
||||
self.depart_admonition(node)
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.add_event('todo-defined')
|
||||
app.add_config_value('todo_include_todos', False, 'html')
|
||||
app.add_config_value('todo_link_only', False, 'html')
|
||||
|
@ -12,6 +12,7 @@
|
||||
import traceback
|
||||
|
||||
from six import iteritems, text_type
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
import sphinx
|
||||
@ -20,10 +21,17 @@ from sphinx.locale import _
|
||||
from sphinx.pycode import ModuleAnalyzer
|
||||
from sphinx.util import get_full_modname
|
||||
from sphinx.util.nodes import make_refnode
|
||||
from sphinx.util.console import blue
|
||||
from sphinx.util.console import blue # type: ignore
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Iterable, Iterator, Tuple # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
|
||||
|
||||
def _get_full_modname(app, modname, attribute):
|
||||
# type: (Sphinx, str, unicode) -> unicode
|
||||
try:
|
||||
return get_full_modname(modname, attribute)
|
||||
except AttributeError:
|
||||
@ -43,20 +51,21 @@ def _get_full_modname(app, modname, attribute):
|
||||
|
||||
|
||||
def doctree_read(app, doctree):
|
||||
# type: (Sphinx, nodes.Node) -> None
|
||||
env = app.builder.env
|
||||
if not hasattr(env, '_viewcode_modules'):
|
||||
env._viewcode_modules = {}
|
||||
env._viewcode_modules = {} # type: ignore
|
||||
if app.builder.name == "singlehtml":
|
||||
return
|
||||
if app.builder.name.startswith("epub") and not env.config.viewcode_enable_epub:
|
||||
return
|
||||
|
||||
def has_tag(modname, fullname, docname, refname):
|
||||
entry = env._viewcode_modules.get(modname, None)
|
||||
entry = env._viewcode_modules.get(modname, None) # type: ignore
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(modname)
|
||||
except Exception:
|
||||
env._viewcode_modules[modname] = False
|
||||
env._viewcode_modules[modname] = False # type: ignore
|
||||
return
|
||||
if not isinstance(analyzer.code, text_type):
|
||||
code = analyzer.code.decode(analyzer.encoding)
|
||||
@ -65,7 +74,7 @@ def doctree_read(app, doctree):
|
||||
if entry is None or entry[0] != code:
|
||||
analyzer.find_tags()
|
||||
entry = code, analyzer.tags, {}, refname
|
||||
env._viewcode_modules[modname] = entry
|
||||
env._viewcode_modules[modname] = entry # type: ignore
|
||||
elif entry is False:
|
||||
return
|
||||
_, tags, used, _ = entry
|
||||
@ -76,7 +85,7 @@ def doctree_read(app, doctree):
|
||||
for objnode in doctree.traverse(addnodes.desc):
|
||||
if objnode.get('domain') != 'py':
|
||||
continue
|
||||
names = set()
|
||||
names = set() # type: Set[unicode]
|
||||
for signode in objnode:
|
||||
if not isinstance(signode, addnodes.desc_signature):
|
||||
continue
|
||||
@ -106,16 +115,18 @@ def doctree_read(app, doctree):
|
||||
|
||||
|
||||
def env_merge_info(app, env, docnames, other):
|
||||
# type: (Sphinx, BuildEnvironment, Iterable[unicode], BuildEnvironment) -> None
|
||||
if not hasattr(other, '_viewcode_modules'):
|
||||
return
|
||||
# create a _viewcode_modules dict on the main environment
|
||||
if not hasattr(env, '_viewcode_modules'):
|
||||
env._viewcode_modules = {}
|
||||
env._viewcode_modules = {} # type: ignore
|
||||
# now merge in the information from the subprocess
|
||||
env._viewcode_modules.update(other._viewcode_modules)
|
||||
env._viewcode_modules.update(other._viewcode_modules) # type: ignore
|
||||
|
||||
|
||||
def missing_reference(app, env, node, contnode):
|
||||
# type: (Sphinx, BuildEnvironment, nodes.Node, nodes.Node) -> nodes.Node
|
||||
# resolve our "viewcode" reference nodes -- they need special treatment
|
||||
if node['reftype'] == 'viewcode':
|
||||
return make_refnode(app.builder, node['refdoc'], node['reftarget'],
|
||||
@ -123,20 +134,21 @@ def missing_reference(app, env, node, contnode):
|
||||
|
||||
|
||||
def collect_pages(app):
|
||||
# type: (Sphinx) -> Iterator[Tuple[unicode, Dict[unicode, Any], unicode]]
|
||||
env = app.builder.env
|
||||
if not hasattr(env, '_viewcode_modules'):
|
||||
return
|
||||
highlighter = app.builder.highlighter
|
||||
highlighter = app.builder.highlighter # type: ignore
|
||||
urito = app.builder.get_relative_uri
|
||||
|
||||
modnames = set(env._viewcode_modules)
|
||||
modnames = set(env._viewcode_modules) # type: ignore
|
||||
|
||||
# app.builder.info(' (%d module code pages)' %
|
||||
# len(env._viewcode_modules), nonl=1)
|
||||
|
||||
for modname, entry in app.status_iterator(
|
||||
iteritems(env._viewcode_modules), 'highlighting module code... ',
|
||||
blue, len(env._viewcode_modules), lambda x: x[0]):
|
||||
iteritems(env._viewcode_modules), 'highlighting module code... ', # type:ignore
|
||||
blue, len(env._viewcode_modules), lambda x: x[0]): # type:ignore
|
||||
if not entry:
|
||||
continue
|
||||
code, tags, used, refname = entry
|
||||
@ -185,7 +197,7 @@ def collect_pages(app):
|
||||
'title': modname,
|
||||
'body': (_('<h1>Source code for %s</h1>') % modname +
|
||||
'\n'.join(lines)),
|
||||
}
|
||||
} # type: Dict[unicode, Any]
|
||||
yield (pagename, context, 'page.html')
|
||||
|
||||
if not modnames:
|
||||
@ -218,6 +230,7 @@ def collect_pages(app):
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
app.add_config_value('viewcode_import', True, False)
|
||||
app.add_config_value('viewcode_enable_epub', False, False)
|
||||
app.connect('doctree-read', doctree_read)
|
||||
|
Loading…
Reference in New Issue
Block a user