sphinx/sphinx/ext/autodoc/directive.py

159 lines
5.8 KiB
Python
Raw Normal View History

2017-12-16 09:03:56 -06:00
"""
sphinx.ext.autodoc.directive
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import warnings
2019-07-13 09:29:38 -05:00
from typing import Any, Callable, Dict, List, Set
2017-12-16 09:03:56 -06:00
from docutils import nodes
from docutils.nodes import Element, Node
from docutils.parsers.rst.states import RSTState, Struct
2018-11-04 09:39:13 -06:00
from docutils.statemachine import StringList
from docutils.utils import Reporter, assemble_option_dict
2017-12-16 09:03:56 -06:00
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.environment import BuildEnvironment
from sphinx.ext.autodoc import Documenter, Options
2017-12-16 09:03:56 -06:00
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective, switch_source_input
2017-12-16 09:03:56 -06:00
from sphinx.util.nodes import nested_parse_with_titles
2019-07-13 09:29:38 -05:00
if False:
# For type annotation
from typing import Type # for python3.5.1
2017-12-16 09:03:56 -06:00
logger = logging.getLogger(__name__)
# common option names for autodoc directives
AUTODOC_DEFAULT_OPTIONS = ['members', 'undoc-members', 'inherited-members',
'show-inheritance', 'private-members', 'special-members',
'ignore-module-all', 'exclude-members', 'member-order',
'imported-members']
2017-12-16 09:03:56 -06:00
2018-11-04 09:39:13 -06:00
class DummyOptionSpec(dict):
"""An option_spec allows any options."""
2017-12-16 09:03:56 -06:00
def __bool__(self) -> bool:
2018-11-04 09:39:13 -06:00
"""Behaves like some options are defined."""
return True
def __getitem__(self, key: str) -> Callable[[str], str]:
return lambda x: x
2017-12-16 09:03:56 -06:00
class DocumenterBridge:
2017-12-16 10:31:32 -06:00
"""A parameters container for Documenters."""
def __init__(self, env: BuildEnvironment, reporter: Reporter, options: Options,
lineno: int, state: Any = None) -> None:
2017-12-16 09:03:56 -06:00
self.env = env
self.reporter = reporter
self.genopt = options
self.lineno = lineno
2018-12-14 12:14:11 -06:00
self.filename_set = set() # type: Set[str]
2018-11-04 09:39:13 -06:00
self.result = StringList()
2017-12-16 09:03:56 -06:00
if state:
self.state = state
else:
# create fake object for self.state.document.settings.tab_width
warnings.warn('DocumenterBridge requires a state object on instantiation.',
RemovedInSphinx40Warning)
settings = Struct(tab_width=8)
document = Struct(settings=settings)
self.state = Struct(document=document)
def warn(self, msg: str) -> None:
logger.warning(msg, location=(self.env.docname, self.lineno))
2017-12-16 09:03:56 -06:00
2019-07-13 09:29:38 -05:00
def process_documenter_options(documenter: "Type[Documenter]", config: Config, options: Dict
) -> Options:
2017-12-16 10:31:32 -06:00
"""Recognize options of Documenter from user input."""
2017-12-16 09:03:56 -06:00
for name in AUTODOC_DEFAULT_OPTIONS:
if name not in documenter.option_spec:
continue
else:
negated = options.pop('no-' + name, True) is None
2018-08-18 03:40:38 -05:00
if name in config.autodoc_default_options and not negated:
options[name] = config.autodoc_default_options[name]
2017-12-16 09:03:56 -06:00
return Options(assemble_option_dict(options.items(), documenter.option_spec))
def parse_generated_content(state: RSTState, content: StringList, documenter: Documenter
) -> List[Node]:
2017-12-16 10:31:32 -06:00
"""Parse a generated content by Documenter."""
with switch_source_input(state, content):
2017-12-16 09:03:56 -06:00
if documenter.titles_allowed:
node = nodes.section() # type: Element
2017-12-16 09:03:56 -06:00
# 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
class AutodocDirective(SphinxDirective):
2017-12-16 09:03:56 -06:00
"""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 = DummyOptionSpec()
2017-12-16 09:03:56 -06:00
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
def run(self) -> List[Node]:
2017-12-16 09:03:56 -06:00
reporter = self.state.document.reporter
try:
2018-12-03 10:52:07 -06:00
source, lineno = reporter.get_source_and_line(self.lineno) # type: ignore
2017-12-16 09:03:56 -06:00
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 = self.env.app.registry.documenters[objtype]
2017-12-16 09:03:56 -06:00
# process the options with the selected documenter's option_spec
try:
documenter_options = process_documenter_options(doccls, self.config, self.options)
2017-12-16 09:03:56 -06:00
except (KeyError, ValueError, TypeError) as exc:
# an option is either unknown or has a wrong type
logger.error('An option to %s is either unknown or has an invalid value: %s' %
(self.name, exc), location=(self.env.docname, lineno))
return []
2017-12-16 09:03:56 -06:00
# generate the output
params = DocumenterBridge(self.env, reporter, documenter_options, lineno, self.state)
2017-12-16 09:03:56 -06:00
documenter = doccls(params, self.arguments[0])
documenter.generate(more_content=self.content)
if not params.result:
return []
2017-12-16 09:03:56 -06:00
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 result