mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-11-02 09:00:00 -06:00
179 lines
5.8 KiB
Python
179 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from typing import TYPE_CHECKING
|
|
|
|
from docutils.statemachine import StringList
|
|
from docutils.utils import assemble_option_dict
|
|
|
|
from sphinx.ext.autodoc._legacy_class_based._directive_options import Options
|
|
from sphinx.ext.autodoc._shared import LOGGER
|
|
from sphinx.util.docutils import SphinxDirective
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Any
|
|
|
|
from docutils.nodes import Node
|
|
from docutils.parsers.rst.states import RSTState
|
|
from docutils.utils import Reporter
|
|
|
|
from sphinx.config import Config
|
|
from sphinx.environment import BuildEnvironment
|
|
from sphinx.ext.autodoc._legacy_class_based._documenters import Documenter
|
|
|
|
|
|
# common option names for autodoc directives
|
|
# Retained: legacy class-based
|
|
AUTODOC_DEFAULT_OPTIONS = [
|
|
'members',
|
|
'undoc-members',
|
|
'no-index',
|
|
'no-index-entry',
|
|
'inherited-members',
|
|
'show-inheritance',
|
|
'private-members',
|
|
'special-members',
|
|
'ignore-module-all',
|
|
'exclude-members',
|
|
'member-order',
|
|
'imported-members',
|
|
'class-doc-from',
|
|
'no-value',
|
|
]
|
|
|
|
# Retained: legacy class-based
|
|
AUTODOC_EXTENDABLE_OPTIONS = frozenset({
|
|
'members',
|
|
'private-members',
|
|
'special-members',
|
|
'exclude-members',
|
|
})
|
|
|
|
|
|
# Retained: legacy class-based
|
|
class DummyOptionSpec(dict[str, Callable[[str], str]]): # ruff: ignore[subclass-builtin]
|
|
"""An option_spec allows any options."""
|
|
|
|
def __bool__(self) -> bool:
|
|
"""Behaves like some options are defined."""
|
|
return True
|
|
|
|
def __getitem__(self, _key: str) -> Callable[[str], str]:
|
|
return lambda x: x
|
|
|
|
|
|
# Retained: legacy class-based
|
|
class DocumenterBridge:
|
|
"""A parameters container for Documenters."""
|
|
|
|
def __init__(
|
|
self,
|
|
env: BuildEnvironment,
|
|
reporter: Reporter | None,
|
|
options: Options,
|
|
lineno: int | None,
|
|
state: Any,
|
|
) -> None:
|
|
self.env = env
|
|
self._reporter = reporter
|
|
self.genopt = options
|
|
self.lineno = lineno
|
|
self.record_dependencies: set[str] = set()
|
|
self.result = StringList()
|
|
self.state = state
|
|
|
|
|
|
# Retained: legacy class-based
|
|
def process_documenter_options(
|
|
documenter: type[Documenter], config: Config, options: dict[str, str]
|
|
) -> Options:
|
|
"""Recognize options of Documenter from user input."""
|
|
default_options = config.autodoc_default_options
|
|
for name in AUTODOC_DEFAULT_OPTIONS:
|
|
if name not in documenter.option_spec:
|
|
continue
|
|
negated = options.pop('no-' + name, True) is None
|
|
if name in default_options and not negated:
|
|
if name in options and isinstance(default_options[name], str):
|
|
# take value from options if present or extend it
|
|
# with autodoc_default_options if necessary
|
|
if name in AUTODOC_EXTENDABLE_OPTIONS:
|
|
if options[name] is not None and options[name].startswith('+'):
|
|
options[name] = f'{default_options[name]},{options[name][1:]}'
|
|
else:
|
|
options[name] = default_options[name]
|
|
|
|
elif options.get(name) is not None:
|
|
# remove '+' from option argument if there's nothing to merge it with
|
|
options[name] = options[name].lstrip('+')
|
|
|
|
return Options(assemble_option_dict(options.items(), documenter.option_spec))
|
|
|
|
|
|
# Retained: legacy class-based
|
|
def parse_generated_content(
|
|
state: RSTState, content: StringList, documenter: Documenter
|
|
) -> list[Node]:
|
|
"""Parse an item of content generated by Documenter."""
|
|
from sphinx.ext.autodoc._directive import parse_generated_content
|
|
|
|
return parse_generated_content(state, content, documenter.titles_allowed)
|
|
|
|
|
|
# Retained: legacy class-based
|
|
class AutodocDirective(SphinxDirective):
|
|
"""A directive class for all autodoc directives. It works as a dispatcher of Documenters.
|
|
|
|
It invokes a Documenter upon running. After the processing, it parses and returns
|
|
the content generated by Documenter.
|
|
"""
|
|
|
|
option_spec = DummyOptionSpec()
|
|
has_content = True
|
|
required_arguments = 1
|
|
optional_arguments = 0
|
|
final_argument_whitespace = True
|
|
|
|
def run(self) -> list[Node]:
|
|
reporter = self.state.document.reporter
|
|
source, lineno = self.get_source_info()
|
|
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._registry.documenters[objtype]
|
|
|
|
# process the options with the selected documenter's option_spec
|
|
try:
|
|
documenter_options = process_documenter_options(
|
|
doccls, self.config, self.options
|
|
)
|
|
except (KeyError, ValueError, TypeError) as exc:
|
|
# an option is either unknown or has a wrong type
|
|
LOGGER.error( # ruff: ignore[error-instead-of-exception]
|
|
'An option to %s is either unknown or has an invalid value: %s',
|
|
self.name,
|
|
exc,
|
|
location=(self.env.current_document.docname, lineno),
|
|
)
|
|
return []
|
|
|
|
# generate the output
|
|
params = DocumenterBridge(
|
|
self.env, reporter, documenter_options, lineno, self.state
|
|
)
|
|
documenter = doccls(params, self.arguments[0])
|
|
documenter.generate(more_content=self.content)
|
|
if not params.result:
|
|
return []
|
|
|
|
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.record_dependencies:
|
|
self.state.document.settings.record_dependencies.add(fn)
|
|
|
|
result = parse_generated_content(self.state, params.result, documenter)
|
|
return result
|