diff --git a/src/_griffe/docstrings/models.py b/src/_griffe/docstrings/models.py index 3aeadbe8..fde423ff 100644 --- a/src/_griffe/docstrings/models.py +++ b/src/_griffe/docstrings/models.py @@ -2,31 +2,43 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from textwrap import indent +from typing import TYPE_CHECKING, Any, Literal from _griffe.enumerations import DocstringSectionKind if TYPE_CHECKING: - from typing import Any, Literal - from _griffe.expressions import Expr +DocstringStyle = Literal["google", "numpy", "sphinx", "auto"] +"""The supported docstring styles (literal values of the Parser enumeration).""" + + # Elements ----------------------------------------------- class DocstringElement: """This base class represents annotated, nameless elements.""" - def __init__(self, *, description: str, annotation: str | Expr | None = None) -> None: + def __init__(self, *, description: str, annotation: str | Expr | None = None, hardcoded: bool = True) -> None: """Initialize the element. Parameters: - annotation: The element annotation, if any. description: The element description. + annotation: The element annotation, if any. + hardcoded: Whether the annotation was hardcoded into the docstring. """ self.description: str = description """The element description.""" self.annotation: str | Expr | None = annotation """The element annotation.""" + self.hardcoded: bool = hardcoded + """Whether the annotation was hardcoded into the docstring. + + It could have been obtained via other means (e.g. type hints). + + This helps to determine whether the annotation should be included + when re-rendering a docstring section. + """ def as_dict(self, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002 """Return this element's data as a dictionary. @@ -53,6 +65,7 @@ def __init__( description: str, annotation: str | Expr | None = None, value: str | None = None, + hardcoded: bool = True, ) -> None: """Initialize the element. @@ -61,8 +74,9 @@ def __init__( description: The element description. annotation: The element annotation, if any. value: The element value, as a string. + hardcoded: Whether the annotation was hardcoded into the docstring. """ - super().__init__(description=description, annotation=annotation) + super().__init__(description=description, annotation=annotation, hardcoded=hardcoded) self.name: str = name """The element name.""" self.value: str | None = value @@ -217,6 +231,12 @@ def as_dict(self, **kwargs: Any) -> dict[str, Any]: base["title"] = self.title return base + def render(self, style: DocstringStyle) -> str: + """Render the section as a string.""" + raise NotImplementedError( + f"Rendering not implemented for sections '{self.__class__.__name__}' and style '{style}'" + ) + class DocstringSectionText(DocstringSection): """This class represents a text section.""" @@ -233,6 +253,10 @@ def __init__(self, value: str, title: str | None = None) -> None: super().__init__(title) self.value: str = value + def render(self, style: DocstringStyle) -> str: + """Render the section as a string.""" + return self.value + class DocstringSectionParameters(DocstringSection): """This class represents a parameters section.""" @@ -249,6 +273,23 @@ def __init__(self, value: list[DocstringParameter], title: str | None = None) -> super().__init__(title) self.value: list[DocstringParameter] = value + def render(self, style: DocstringStyle) -> str: + """Render the section as a string.""" + return { + "google": self.render_google, + "numpy": self.render_numpy, + "sphinx": self.render_sphinx, + }.get(style, super().render)() + + def render_google(self) -> str: + """Render the section in Google style.""" + lines = ["Parameters:"] + for param in self.value: + annotation = f" ({param.annotation})" if param.annotation and param.hardcoded else "" + lines.append(f" {param.name}{annotation}:") + lines.extend(indent(param.description, " " * 8).splitlines()) + return "\n".join(lines) + class DocstringSectionOtherParameters(DocstringSectionParameters): """This class represents an other parameters section.""" diff --git a/src/_griffe/docstrings/parsers.py b/src/_griffe/docstrings/parsers.py index 9114b018..1d9f9e07 100644 --- a/src/_griffe/docstrings/parsers.py +++ b/src/_griffe/docstrings/parsers.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, Callable, Literal from _griffe.docstrings.google import parse_google -from _griffe.docstrings.models import DocstringSection, DocstringSectionText +from _griffe.docstrings.models import DocstringSection, DocstringSectionText, DocstringStyle from _griffe.docstrings.numpy import parse_numpy from _griffe.docstrings.sphinx import parse_sphinx from _griffe.enumerations import Parser @@ -22,9 +22,6 @@ # in plain markup docstrings too, even more often than Google sections. _default_style_order = [Parser.sphinx, Parser.google, Parser.numpy] - -DocstringStyle = Literal["google", "numpy", "sphinx", "auto"] -"""The supported docstring styles (literal values of the Parser enumeration).""" DocstringDetectionMethod = Literal["heuristics", "max_sections"] """The supported methods to infer docstring styles."""