"""Handle sphinx logging."""
from __future__ import annotations
import typing as t
from docutils.nodes import Element
from docutils.statemachine import StringList
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.util import logging
from autodoc2.config import CONFIG_PREFIX, Config, ValidationError
from autodoc2.db import Database, InMemoryDb
from autodoc2.resolve_all import AllResolver
from autodoc2.utils import WarningSubtypes
if t.TYPE_CHECKING:
from docutils.parsers.rst.states import RSTStateMachine
LOGGER = logging.getLogger("autodoc2")
[docs]def load_config(
app: Sphinx,
*,
overrides: None | dict[str, t.Any] = None,
location: None | Element = None,
) -> Config:
"""Load the configuration."""
values: dict[str, t.Any] = {}
overrides = overrides or {}
config_fields = {name: field for name, _, field in Config().as_triple()}
# check if keys in overrides are valid
difference = set(overrides.keys()) - set(config_fields.keys())
if difference:
warn_sphinx(
f"Unknown configuration keys: {', '.join(difference)}",
WarningSubtypes.CONFIG_ERROR,
location,
)
for name, field in config_fields.items():
sphinx_name = f"{CONFIG_PREFIX}{name}"
value = overrides.get(name, app.config[sphinx_name])
if "sphinx_validate" in field.metadata:
try:
value = field.metadata["sphinx_validate"](sphinx_name, value)
except ValidationError as err:
warn_sphinx(str(err), WarningSubtypes.CONFIG_ERROR, location)
continue
values[name] = value
return Config(**values)
[docs]def warn_sphinx(
msg: str, subtype: WarningSubtypes, location: None | Element = None
) -> None:
"""Log a warning in Sphinx."""
LOGGER.warning(
f"{msg} [autodoc2.{subtype.value}]",
type="autodoc2",
subtype=subtype.value,
location=location,
)
[docs]def get_database(env: BuildEnvironment) -> Database:
"""Get the database from the environment."""
# We store it persistently in the environment so that it can be
# accessed by directives and other extensions.
if not hasattr(env, "autodoc2_db"):
env.autodoc2_db = InMemoryDb() # type: ignore
return env.autodoc2_db # type: ignore
[docs]def _warn(msg: str) -> None:
warn_sphinx(msg, WarningSubtypes.ALL_RESOLUTION)
[docs]def get_all_analyser(env: BuildEnvironment) -> AllResolver:
"""Get the all analyser from the environment."""
# We store it persistently in the environment so that it can be
# accessed by directives and other extensions.
if not hasattr(env, "autodoc2_all_analyser"):
env.autodoc2_all_analyser = AllResolver(get_database(env), _warn) # type: ignore
return env.autodoc2_all_analyser # type: ignore
[docs]def nested_parse_generated(
state: RSTStateMachine,
content: list[str],
source: str,
line: int,
*,
match_titles: bool = False,
base: Element | None = None,
) -> Element:
"""This function, nested parses generated content in a directive.
All reported warnings are redirected to a specific source document and line.
This is useful for directives that want to parse generated content.
"""
base = Element() if base is None else base
base.source = source
base.line = line
string_list = StringList(
content,
items=[(source, line - 1) for _ in content],
)
line_func = getattr(state.reporter, "get_source_and_line", None)
state.reporter.get_source_and_line = lambda li: (
source,
line,
)
try:
state.nested_parse(string_list, 0, base, match_titles=match_titles)
finally:
if line_func is not None:
state.reporter.get_source_and_line = line_func
else:
del state.reporter.get_source_and_line
return base