From cf7da1843c45a4c2df7a749f7886a2d2ba0ee92a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 19:25:40 +0200 Subject: Adding upstream version 7.2.6. Signed-off-by: Daniel Baumann --- sphinx/ext/autodoc/directive.py | 151 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 sphinx/ext/autodoc/directive.py (limited to 'sphinx/ext/autodoc/directive.py') diff --git a/sphinx/ext/autodoc/directive.py b/sphinx/ext/autodoc/directive.py new file mode 100644 index 0000000..64cbc9b --- /dev/null +++ b/sphinx/ext/autodoc/directive.py @@ -0,0 +1,151 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Callable + +from docutils import nodes +from docutils.statemachine import StringList +from docutils.utils import Reporter, assemble_option_dict + +from sphinx.ext.autodoc import Documenter, Options +from sphinx.util import logging +from sphinx.util.docutils import SphinxDirective, switch_source_input +from sphinx.util.nodes import nested_parse_with_titles + +if TYPE_CHECKING: + from docutils.nodes import Element, Node + from docutils.parsers.rst.states import RSTState + + from sphinx.config import Config + from sphinx.environment import BuildEnvironment + +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', 'class-doc-from', 'no-value'] + +AUTODOC_EXTENDABLE_OPTIONS = ['members', 'private-members', 'special-members', + 'exclude-members'] + + +class DummyOptionSpec(dict): + """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 + + +class DocumenterBridge: + """A parameters container for Documenters.""" + + def __init__(self, env: BuildEnvironment, reporter: Reporter | None, options: Options, + lineno: int, 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 + + +def process_documenter_options(documenter: type[Documenter], config: Config, options: dict, + ) -> Options: + """Recognize options of Documenter from user input.""" + 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 config.autodoc_default_options and not negated: + if name in options and isinstance(config.autodoc_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] = ','.join([config.autodoc_default_options[name], + options[name][1:]]) + else: + options[name] = config.autodoc_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)) + + +def parse_generated_content(state: RSTState, content: StringList, documenter: Documenter, + ) -> list[Node]: + """Parse an item of content generated by Documenter.""" + with switch_source_input(state, content): + if documenter.titles_allowed: + node: Element = 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 + + +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 + + try: + source, lineno = reporter.get_source_and_line( # type: ignore[attr-defined] + 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 = self.env.app.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('An option to %s is either unknown or has an invalid value: %s' % + (self.name, exc), location=(self.env.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 -- cgit v1.2.3