mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
autodoc: refactor AutoDirective
This commit is contained in:
parent
f07b080331
commit
832914423e
@ -659,8 +659,9 @@ class Sphinx(object):
|
||||
# type: (Any) -> None
|
||||
logger.debug('[app] adding autodocumenter: %r', cls)
|
||||
from sphinx.ext import autodoc
|
||||
from sphinx.ext.autodoc.directive import AutodocDirective
|
||||
autodoc.add_documenter(cls)
|
||||
self.add_directive('auto' + cls.objtype, autodoc.AutoDirective)
|
||||
self.add_directive('auto' + cls.objtype, AutodocDirective)
|
||||
|
||||
def add_autodoc_attrgetter(self, type, getter):
|
||||
# type: (Any, Callable) -> None
|
||||
|
@ -18,8 +18,6 @@ import traceback
|
||||
|
||||
from six import PY2, iterkeys, iteritems, itervalues, text_type, class_types, string_types
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.utils import assemble_option_dict
|
||||
from docutils.parsers.rst import Directive
|
||||
from docutils.statemachine import ViewList
|
||||
|
||||
@ -32,7 +30,6 @@ from sphinx.locale import _
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.application import ExtensionError
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.nodes import nested_parse_with_titles
|
||||
from sphinx.util.inspect import Signature, isdescriptor, safe_getmembers, \
|
||||
safe_getattr, object_description, is_builtin_class_method, \
|
||||
isenumclass, isenumattribute, getdoc
|
||||
@ -42,11 +39,11 @@ if False:
|
||||
# For type annotation
|
||||
from types import ModuleType # NOQA
|
||||
from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union # NOQA
|
||||
from docutils.utils import Reporter # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# This type isn't exposed directly in any modules, but can be found
|
||||
# here in most Python versions
|
||||
MethodDescriptorType = type(type.__subclasses__)
|
||||
@ -63,42 +60,11 @@ py_ext_sig_re = re.compile(
|
||||
''', re.VERBOSE)
|
||||
|
||||
|
||||
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:
|
||||
return None
|
||||
|
||||
|
||||
ALL = object()
|
||||
INSTANCEATTR = object()
|
||||
|
||||
@ -1525,93 +1491,6 @@ class AutoDirective(Directive):
|
||||
# a registry of type -> getattr function
|
||||
_special_attrgetters = {} # type: Dict[Type, Callable]
|
||||
|
||||
# flags that can be given in autodoc_default_flags
|
||||
_default_flags = set([
|
||||
'members', 'undoc-members', 'inherited-members', 'show-inheritance',
|
||||
'private-members', 'special-members',
|
||||
])
|
||||
|
||||
# standard docutils directive settings
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
# allow any options to be passed; the options are parsed further
|
||||
# by the selected Documenter
|
||||
option_spec = DefDict(identity)
|
||||
|
||||
def warn(self, msg):
|
||||
# type: (unicode) -> None
|
||||
self.warnings.append(self.reporter.warning(msg, line=self.lineno))
|
||||
|
||||
def run(self):
|
||||
# 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 = [] # type: List[unicode]
|
||||
self.result = ViewList()
|
||||
|
||||
try:
|
||||
source, lineno = self.reporter.get_source_and_line(self.lineno)
|
||||
except AttributeError:
|
||||
source = lineno = None
|
||||
logger.debug('[autodoc] %s:%s: input:\n%s',
|
||||
source, lineno, self.block_text)
|
||||
|
||||
# find out what documenter to call
|
||||
objtype = self.name[4:]
|
||||
doc_class = self._registry[objtype]
|
||||
# add default flags
|
||||
for flag in self._default_flags:
|
||||
if flag not in doc_class.option_spec:
|
||||
continue
|
||||
negated = self.options.pop('no-' + flag, 'not given') is None
|
||||
if flag in self.env.config.autodoc_default_flags and \
|
||||
not negated:
|
||||
self.options[flag] = None
|
||||
# process the options with the selected documenter's option_spec
|
||||
try:
|
||||
self.genopt = Options(assemble_option_dict(
|
||||
self.options.items(), doc_class.option_spec))
|
||||
except (KeyError, ValueError, TypeError) as err:
|
||||
# an option is either unknown or has a wrong type
|
||||
msg = self.reporter.error('An option to %s is either unknown or '
|
||||
'has an invalid value: %s' % (self.name, err),
|
||||
line=self.lineno)
|
||||
return [msg]
|
||||
# generate the output
|
||||
documenter = doc_class(self, self.arguments[0])
|
||||
documenter.generate(more_content=self.content)
|
||||
if not self.result:
|
||||
return self.warnings
|
||||
|
||||
logger.debug('[autodoc] output:\n%s', '\n'.join(self.result))
|
||||
|
||||
# record all filenames as dependencies -- this will at least
|
||||
# partially make automatic invalidation possible
|
||||
for fn in self.filename_set:
|
||||
self.state.document.settings.record_dependencies.add(fn)
|
||||
|
||||
# use a custom reporter that correctly assigns lines to source
|
||||
# filename/description and lineno
|
||||
old_reporter = self.state.memo.reporter
|
||||
self.state.memo.reporter = AutodocReporter(self.result,
|
||||
self.state.memo.reporter)
|
||||
|
||||
if documenter.titles_allowed:
|
||||
node = nodes.section()
|
||||
# necessary so that the child nodes get the right source/line set
|
||||
node.document = self.state.document
|
||||
nested_parse_with_titles(self.state, self.result, node)
|
||||
else:
|
||||
node = nodes.paragraph()
|
||||
node.document = self.state.document
|
||||
self.state.nested_parse(self.result, 0, node)
|
||||
self.state.memo.reporter = old_reporter
|
||||
return self.warnings + node.children
|
||||
|
||||
|
||||
def add_documenter(cls):
|
||||
# type: (Type[Documenter]) -> None
|
||||
|
172
sphinx/ext/autodoc/directive.py
Normal file
172
sphinx/ext/autodoc/directive.py
Normal file
@ -0,0 +1,172 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sphinx.ext.autodoc.directive
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive
|
||||
from docutils.statemachine import ViewList
|
||||
from docutils.utils import assemble_option_dict
|
||||
|
||||
from sphinx.ext.autodoc import AutoDirective, AutodocReporter, identity
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.nodes import nested_parse_with_titles
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Dict, List, Set, Type # NOQA
|
||||
from docutils.statemachine import State, StateMachine, StringList # NOQA
|
||||
from docutils.utils import Reporter # NOQA
|
||||
from sphinx.config import Config # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
from sphinx.ext.autodoc import Documenter # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# common option names for autodoc directives
|
||||
AUTODOC_DEFAULT_OPTIONS = ['members', 'undoc-members', 'inherited-members',
|
||||
'show-inheritance', 'private-members', 'special-members']
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
return None
|
||||
|
||||
|
||||
class DocumenterBridge(object):
|
||||
def __init__(self, env, reporter, options, lineno):
|
||||
# type: (BuildEnvironment, Reporter, Options, int) -> None
|
||||
self.env = env
|
||||
self.reporter = reporter
|
||||
self.genopt = options
|
||||
self.lineno = lineno
|
||||
self.filename_set = set() # type: Set[unicode]
|
||||
self.warnings = [] # type: List[nodes.Node]
|
||||
self.result = ViewList()
|
||||
|
||||
def warn(self, msg):
|
||||
# type: (unicode) -> None
|
||||
self.warnings.append(self.reporter.warning(msg, line=self.lineno))
|
||||
|
||||
|
||||
def process_documenter_options(documenter, config, options):
|
||||
# type: (Type[Documenter], Config, Dict) -> Options
|
||||
for name in AUTODOC_DEFAULT_OPTIONS:
|
||||
if name not in documenter.option_spec:
|
||||
continue
|
||||
else:
|
||||
negated = options.pop('no-' + name, True) is None
|
||||
if name in config.autodoc_default_flags and not negated:
|
||||
options[name] = None
|
||||
|
||||
return Options(assemble_option_dict(options.items(), documenter.option_spec))
|
||||
|
||||
|
||||
def parse_generated_content(state, content, documenter):
|
||||
# type: (State, StringList, Documenter) -> List[nodes.Node]
|
||||
try:
|
||||
# use a custom reporter that correctly assigns lines to source
|
||||
# filename/description and lineno
|
||||
old_reporter = state.memo.reporter
|
||||
state.memo.reporter = AutodocReporter(content, state.memo.reporter)
|
||||
|
||||
if documenter.titles_allowed:
|
||||
node = nodes.section()
|
||||
# necessary so that the child nodes get the right source/line set
|
||||
node.document = state.document
|
||||
nested_parse_with_titles(state, content, node)
|
||||
else:
|
||||
node = nodes.paragraph()
|
||||
node.document = state.document
|
||||
state.nested_parse(content, 0, node)
|
||||
|
||||
return node.children
|
||||
finally:
|
||||
state.memo.reporter = old_reporter
|
||||
|
||||
|
||||
class AutodocDirective(Directive):
|
||||
"""A directive class for all autodoc directives. It works as a dispatcher of Documenters.
|
||||
|
||||
It invokes a Documenter on running. After the processing, it parses and returns
|
||||
the generated content by Documenter.
|
||||
"""
|
||||
option_spec = DefDict(identity)
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
env = self.state.document.settings.env
|
||||
reporter = self.state.document.reporter
|
||||
|
||||
try:
|
||||
source, lineno = reporter.get_source_and_line(self.lineno)
|
||||
except AttributeError:
|
||||
source, lineno = (None, None)
|
||||
logger.debug('[autodoc] %s:%s: input:\n%s', source, lineno, self.block_text)
|
||||
|
||||
# look up target Documenter
|
||||
objtype = self.name[4:] # strip prefix (auto-).
|
||||
doccls = AutoDirective._registry[objtype]
|
||||
|
||||
# process the options with the selected documenter's option_spec
|
||||
try:
|
||||
documenter_options = process_documenter_options(doccls, env.config, self.options)
|
||||
except (KeyError, ValueError, TypeError) as exc:
|
||||
# an option is either unknown or has a wrong type
|
||||
msg = reporter.error('An option to %s is either unknown or '
|
||||
'has an invalid value: %s' % (self.name, exc),
|
||||
line=lineno)
|
||||
return [msg]
|
||||
|
||||
# generate the output
|
||||
params = DocumenterBridge(env, reporter, documenter_options, lineno)
|
||||
documenter = doccls(params, self.arguments[0])
|
||||
documenter.generate(more_content=self.content)
|
||||
if not params.result:
|
||||
return params.warnings
|
||||
|
||||
logger.debug('[autodoc] output:\n%s', '\n'.join(params.result))
|
||||
|
||||
# record all filenames as dependencies -- this will at least
|
||||
# partially make automatic invalidation possible
|
||||
for fn in params.filename_set:
|
||||
self.state.document.settings.record_dependencies.add(fn)
|
||||
|
||||
result = parse_generated_content(self.state, params.result, documenter)
|
||||
return params.warnings + result
|
@ -72,7 +72,7 @@ from sphinx import addnodes
|
||||
from sphinx.environment.adapters.toctree import TocTree
|
||||
from sphinx.util import import_object, rst, logging
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.ext.autodoc import Options
|
||||
from sphinx.ext.autodoc.directive import Options
|
||||
from sphinx.ext.autodoc.importer import import_module
|
||||
|
||||
if False:
|
||||
|
@ -31,7 +31,7 @@ report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Iterator, List, Tuple # NOQA
|
||||
from typing import Any, Callable, Dict, Iterator, List, Tuple # NOQA
|
||||
from docutils import nodes # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
from sphinx.io import SphinxFileInput # NOQA
|
||||
@ -204,12 +204,12 @@ def is_html5_writer_available():
|
||||
return __version_info__ > (0, 13, 0)
|
||||
|
||||
|
||||
def directive_helper(obj, has_content=None, argument_spec=None, **option_spec):
|
||||
# type: (Any, bool, Tuple[int, int, bool], Any) -> Any
|
||||
def directive_helper(obj, has_content=None, argument_spec=None, option_spec=None, **options):
|
||||
# type: (Any, bool, Tuple[int, int, bool], Dict, Any) -> Any
|
||||
if isinstance(obj, (types.FunctionType, types.MethodType)):
|
||||
obj.content = has_content # type: ignore
|
||||
obj.arguments = argument_spec or (0, 0, False) # type: ignore
|
||||
obj.options = option_spec # type: ignore
|
||||
obj.options = option_spec or options # type: ignore
|
||||
return convert_directive_function(obj)
|
||||
else:
|
||||
if has_content or argument_spec or option_spec:
|
||||
|
Loading…
Reference in New Issue
Block a user