Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,20 @@ Use the `-o`, `--output` option to write to a file instead of standard output:
```bash
griffe2md markdown -o markdown.md
```

`griffe2md` can be configured in either `pyproject.toml` or a `griffe2md.toml` file. The latter can be placed in a `.config` or `config` directory in the project root.

`griffe2md.toml` file is structured as a simple key-value dictionary, e.g.:

```toml
docstring_style = "sphinx"
```

If you configure it in `pyproject.toml`, the configuration should go under the `tool.griffe2md` key:

```toml
[tool.griffe2md]
docstring_style = "sphinx"
```

See [the documentation](https://mkdocstrings.github.io/griffe2md/reference/griffe2md/config/#griffe2md.config.ConfigDict) for reference.
2 changes: 1 addition & 1 deletion duties.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def coverage(ctx: Context) -> None:


@duty
def test(ctx: Context, *cli_args: str, match: str = "") -> None:
def test(ctx: Context, *cli_args: str, match: str = "") -> None: # noqa: PT028
"""Run the test suite.

Parameters:
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ dependencies = [
"griffe>=0.49",
"jinja2>=3.1.2",
"mdformat>=0.7.16",
# YORE: EOL 3.10: Remove line.
"tomli>=2.0; python_version < '3.11'",
]

[project.urls]
Expand Down
5 changes: 4 additions & 1 deletion src/griffe2md/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing import Any

from griffe2md import debug
from griffe2md.config import load_config
from griffe2md.main import write_package_docs


Expand Down Expand Up @@ -57,5 +58,7 @@ def main(args: list[str] | None = None) -> int:
"""
parser = get_parser()
opts = parser.parse_args(args=args)
write_package_docs(opts.package, output=opts.output)
config = load_config()

write_package_docs(opts.package, config, opts.output)
return 0
256 changes: 256 additions & 0 deletions src/griffe2md/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
"""Load configuration."""

from __future__ import annotations

import logging
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Literal, TypedDict, cast

# YORE: EOL 3.10: Replace block with line 2.
if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib

if TYPE_CHECKING:
from re import Pattern

logger = logging.getLogger(__name__)

CONFIG_FILE_PATHS = (
Path(".config/griffe2md.toml"),
Path("config/griffe2md.toml"),
Path("pyproject.toml"),
)


def _locate_config_file() -> Path | None:
for path in CONFIG_FILE_PATHS:
if path.is_file():
return path
return None


def load_config() -> ConfigDict | None:
"""Load the configuration if config file or config entry in pyproject.toml exists.

If neither config file was found or pyproject.toml file doesn't have
a `[tool.griffe2md]` section, None is returned.
"""
if not (config_path := _locate_config_file()):
return None

logger.debug("Loading config from %s", config_path)

with config_path.open("rb") as f:
config = tomllib.load(f)

if config_path.name == "pyproject.toml":
return config.get("tool", {}).get("griffe2md", None)
return cast("ConfigDict", config)


class ConfigDict(TypedDict):
"""Configuration for griffe2md, griffe and mkdocstrings."""

allow_inspection: bool
"""Allow using introspection on modules for which sources aren't available (compiled modules, etc.)."""

annotations_path: Literal["brief", "source", "full"]
"""The verbosity for annotations path: `brief` (recommended), `source` (as written in the source), or `full`."""

docstring_options: dict
"""mkdocstring [configuration](https://mkdocstrings.github.io/python/usage/configuration/general/)"""

docstring_section_style: Literal["list", "table"]
"""The style used to render docstring sections."""

docstring_style: Literal["google", "numpy", "sphinx", "auto"] | None
"""The style in which docstrings are written: `auto`, `google`, `numpy`, `sphinx`, or `None`."""

filters: list[str] | list[tuple[Pattern[str], bool]]
"""A list of filters.

A filter starting with `!` will exclude matching objects instead of including them.
The `members` option takes precedence over `filters` (filters will still be applied recursively
to lower members in the hierarchy).
"""

group_by_category: bool
"""Group the object's children by categories: attributes, classes, functions, and modules."""

heading_level: int
"""The initial heading level to use."""

inherited_members: bool | list[str]
"""A boolean, or an explicit list of inherited members to render.

If true, select all inherited members, which can then be filtered with `members`.
If false or empty list, do not select any inherited member.
"""

line_length: int
"""Maximum line length when formatting code/signatures."""

load_external_modules: bool
"""Whether to always load external modules/packages."""

members: list[str] | bool | None
"""A boolean, or an explicit list of members to render.

If true, select all members without further filtering.
If false or empty list, do not render members.
If none, select all members and apply further filtering with filters and docstrings.
"""

members_order: Literal["alphabetical", "source"]
"""The members ordering to use.

- `alphabetical`: order members alphabetically;
- `source`: order members as they appear in the source file.
"""

merge_init_into_class: bool
"""Whether to merge the `__init__` method into the class' signature and docstring."""

preload_modules: list[str] | None
"""Pre-load modules that are not specified directly in autodoc instructions (`::: identifier`).

It is useful when you want to render documentation for a particular member of an object,
and this member is imported from another package than its parent.

For an imported member to be rendered, you need to add it to the `__all__` attribute
of the importing module.

The modules must be listed as an array of strings.
"""

separate_signature: bool
"""Whether to put the whole signature in a code block below the heading.

If Black or Ruff are installed, the signature is also formatted using them.
"""

show_bases: bool
"""Show the base classes of a class."""

show_category_heading: bool
"""When grouped by categories, show a heading for each category."""

show_docstring_attributes: bool
"""Whether to display the 'Attributes' section in the object's docstring."""

show_docstring_classes: bool
"""Whether to display the 'Classes' section in the object's docstring."""

show_docstring_description: bool
"""Whether to display the textual block (including admonitions) in the object's docstring."""

show_docstring_examples: bool
"""Whether to display the 'Examples' section in the object's docstring."""

show_docstring_functions: bool
"""Whether to display the 'Functions' section in the object's docstring."""

show_docstring_modules: bool
"""Whether to display the 'Modules' section in the object's docstring."""

show_docstring_other_parameters: bool
"""Whether to display the 'Other Parameters' section in the object's docstring."""

show_docstring_parameters: bool
"""Whether to display the 'Parameters' section in the object's docstring."""

show_docstring_raises: bool
"""Whether to display the 'Raises' section in the object's docstring."""

show_docstring_receives: bool
"""Whether to display the 'Receives' section in the object's docstring."""

show_docstring_returns: bool
"""Whether to display the 'Returns' section in the object's docstring."""

show_docstring_warns: bool
"""Whether to display the 'Warns' section in the object's docstring."""

show_docstring_yields: bool
"""Whether to display the 'Yields' section in the object's docstring."""

show_if_no_docstring: bool
"""Show the object heading even if it has no docstring or children with docstrings."""

show_object_full_path: bool
"""Show the full Python path of every object."""

show_root_full_path: bool
"""Show the full Python path for the root object heading."""

show_root_heading: bool
"""Show the heading of the object at the root of the documentation tree.

The root object is the object referenced by the identifier after `:::`.
"""

show_root_members_full_path: bool
"""Show the full Python path of the root members."""

show_signature: bool
"""Show methods and functions signatures."""

show_signature_annotations: bool
"""Show the type annotations in methods and functions signatures."""

show_submodules: bool
"""When rendering a module, show its submodules recursively."""

signature_crossrefs: bool
"""Whether to render cross-references for type annotations in signatures."""

summary: bool | dict
"""Whether to render summaries of modules, classes, functions (methods) and attributes."""


default_config: ConfigDict = {
"docstring_style": "google",
"docstring_options": {"ignore_init_summary": True},
"show_root_heading": True,
"show_root_full_path": True,
"show_root_members_full_path": True,
"show_object_full_path": True,
"show_category_heading": False,
"show_if_no_docstring": True,
"show_signature": True,
"show_signature_annotations": False,
"signature_crossrefs": False,
"separate_signature": True,
"line_length": 80,
"merge_init_into_class": True,
"show_docstring_attributes": True,
"show_docstring_description": True,
"show_docstring_examples": True,
"show_docstring_other_parameters": True,
"show_docstring_parameters": True,
"show_docstring_raises": True,
"show_docstring_receives": True,
"show_docstring_returns": True,
"show_docstring_warns": True,
"show_docstring_yields": True,
"show_bases": True,
"show_submodules": True,
"group_by_category": False,
"heading_level": 2,
"members_order": "alphabetical",
"docstring_section_style": "list",
"members": None,
"inherited_members": True,
"filters": ["!^_"],
"annotations_path": "brief",
"preload_modules": None,
"load_external_modules": False,
"allow_inspection": True,
"summary": True,
"show_docstring_classes": True,
"show_docstring_functions": True,
"show_docstring_modules": True,
}
26 changes: 17 additions & 9 deletions src/griffe2md/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
import re
import sys
from pathlib import Path
from typing import IO, TYPE_CHECKING
from typing import IO, TYPE_CHECKING, cast

import mdformat
from griffe import GriffeLoader, Parser
from jinja2 import Environment, FileSystemLoader

from griffe2md import rendering
from griffe2md.config import ConfigDict, default_config

if TYPE_CHECKING:
from griffe import Object
Expand All @@ -27,7 +28,7 @@ def _output(text: str, to: IO | str | None = None) -> None:
to.write(text)


def prepare_context(obj: Object, config: dict | None = None) -> dict:
def prepare_context(obj: Object, config: ConfigDict | None = None) -> dict:
"""Prepare Jinja context.

Parameters:
Expand All @@ -37,13 +38,16 @@ def prepare_context(obj: Object, config: dict | None = None) -> dict:
Returns:
The Jinja context.
"""
config = dict(rendering.default_config, **(config or {}))
config = cast("ConfigDict", {**default_config, **(config or {})})
if config["filters"]:
config["filters"] = [(re.compile(filtr.lstrip("!")), filtr.startswith("!")) for filtr in config["filters"]]
config["filters"] = [
(re.compile(filtr.lstrip("!")), filtr.startswith("!")) if isinstance(filtr, str) else filtr
for filtr in config["filters"]
]

heading_level = config["heading_level"]
try:
config["members_order"] = rendering.Order(config["members_order"])
config["members_order"] = rendering.Order(config["members_order"]).value
except ValueError as error:
choices = "', '".join(item.value for item in rendering.Order)
raise ValueError(
Expand Down Expand Up @@ -113,7 +117,7 @@ def prepare_env(env: Environment | None = None) -> Environment:
return env


def render_object_docs(obj: Object, config: dict | None = None) -> str:
def render_object_docs(obj: Object, config: ConfigDict | None = None) -> str:
"""Render docs for a given object.

Parameters:
Expand All @@ -129,7 +133,7 @@ def render_object_docs(obj: Object, config: dict | None = None) -> str:
return mdformat.text(rendered)


def render_package_docs(package: str, config: dict | None = None) -> str:
def render_package_docs(package: str, config: ConfigDict | None = None) -> str:
"""Render docs for a given package.

Parameters:
Expand All @@ -139,15 +143,19 @@ def render_package_docs(package: str, config: dict | None = None) -> str:
Returns:
Markdown.
"""
config = config or dict(rendering.default_config)
config = cast("ConfigDict", {**default_config, **(config or {})})
parser = config["docstring_style"] and Parser(config["docstring_style"])
loader = GriffeLoader(docstring_parser=parser)
module = loader.load(package)
loader.resolve_aliases(external=True)
return render_object_docs(module, config) # type: ignore[arg-type]


def write_package_docs(package: str, config: dict | None = None, output: IO | str | None = None) -> None:
def write_package_docs(
package: str,
config: ConfigDict | None = None,
output: IO | str | None = None,
) -> None:
"""Write docs for a given package to a file or stdout.

Parameters:
Expand Down
Loading
Loading