Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Update documentation templates and macros #38

Merged
merged 3 commits into from
Oct 22, 2024
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
11 changes: 6 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,9 @@ repos:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/PyCQA/docformatter
rev: dfefe062799848234b4cd60b04aa633c0608025e # frozen: v1.7.5
hooks:
- id: docformatter
additional_dependencies: [tomli]
# TODO: Re-enable this once https://github.com/PyCQA/docformatter/issues/293 is resolved
# - repo: https://github.com/PyCQA/docformatter
# rev: dfefe062799848234b4cd60b04aa633c0608025e # frozen: v1.7.5
# hooks:
# - id: docformatter
# additional_dependencies: [tomli]
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This template iterates on members of a given object and renders them.
It can group members by category (attributes, classes, functions, modules) or render them in a flat list.

Context:
obj (griffe.dataclasses.Object): The object to render.
obj (griffe.Object): The object to render.
config (dict): The configuration options.
root_members (bool): Whether the object is the root object.
heading_level (int): The HTML heading level to use.
Expand All @@ -31,7 +31,7 @@ Context:
</table>
{%- endmacro -%}

{% if obj.members %}
{% if obj.all_members %}
{% block logs scoped %}
{#- Logging block.

Expand Down Expand Up @@ -97,7 +97,7 @@ Context:
filters=config.filters,
members_list=members_list,
inherited_members=config.inherited_members,
keep_no_docstrings=config.show_if_no_docstring)|selectattr("is_init_module", "equalto", false)|list|order_members(config.members_order, members_list)
keep_no_docstrings=config.show_if_no_docstring)|selectattr("is_init_module", "equalto", false)|list|order_members(config.members_order, members_list)|sort(attribute="name")
%}
{% if modules %}
{% filter heading(heading_level, id=html_id ~ "-modules") %}Modules{% endfilter %}
Expand Down Expand Up @@ -127,7 +127,7 @@ Context:
{% endif %}
{% with heading_level = heading_level + extra_level %}
{% for attribute in attributes|order_members(config.members_order, members_list) %}
{% if members_list is not none or attribute.is_public %}
{% if members_list is not none or (not attribute.is_imported or attribute.is_public) %}
{% include attribute|get_template with context %}
{% endif %}
{% endfor %}
Expand All @@ -147,7 +147,7 @@ Context:
{% endif %}
{% with heading_level = heading_level + extra_level %}
{% for class in classes|order_members(config.members_order, members_list) %}
{% if members_list is not none or class.is_public %}
{% if members_list is not none or (not class.is_imported or class.is_public) %}
{% include class|get_template with context %}
{% endif %}
{% endfor %}
Expand All @@ -168,7 +168,7 @@ Context:
{% with heading_level = heading_level + extra_level %}
{% for function in functions|order_members(config.members_order, members_list) %}
{% if not (obj.kind.value == "class" and function.name == "__init__" and config.merge_init_into_class) %}
{% if members_list is not none or function.is_public %}
{% if members_list is not none or (not function.is_imported or function.is_public) %}
{% include function|get_template with context %}
{% endif %}
{% endif %}
Expand All @@ -189,8 +189,8 @@ Context:
{% filter heading(heading_level, id=html_id ~ "-modules") %}Modules{% endfilter %}
{% endif %}
{% with heading_level = heading_level + extra_level %}
{% for module in modules|order_members(config.members_order, members_list) %}
{% if members_list is not none or module.is_public %}
{% for module in modules|order_members(config.members_order.alphabetical, members_list) %}
{% if members_list is not none or (not module.is_alias or module.is_public) %}
{% include module|get_template with context %}
{% endif %}
{% endfor %}
Expand Down
109 changes: 92 additions & 17 deletions docs/macros.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Macros for the documentation."""

import abc
import inspect
import os
import pathlib
Expand All @@ -10,7 +11,7 @@

import tomli

from mkdocs_macros.plugin import MacrosPlugin # pyright: ignore[reportMissingTypeStubs]
from mkdocs_macros.plugin import MacrosPlugin

HEADER_ONE_REGEX = re.compile(r"^#\s(.+)$", re.MULTILINE)
PAGE_REPLACEMENTS = {
Expand All @@ -23,11 +24,44 @@
FILES_TO_REMOVE_BLACK_FORMATTER_DISABLE_COMMENT = {
"basic_usage.md",
}
CONVERSION_PATTERN = re.compile(
r"> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|DANGER)]\s*>\s*(.*?)(?=\n[^>]|$)",
re.IGNORECASE | re.DOTALL,
)


####################################################################################################
# Helper functions
####################################################################################################
def convert_gfm_alerts_to_admonitions(content: str) -> str:
"""Convert GitHub Flavored Markdown (GFM) alerts to MkDocs admonitions.

Args:
content: The content to convert.

Returns:
The updated content with GFM alerts converted to markdown admonitions.
"""

def replace_match(match: "re.Match[str]") -> str:
"""Replace the matched GFM alert with an admonition.

Args:
match: The matched GFM alert.

Returns:
The replacement text.
"""
alert_type = match.group(1).lower()
text = match.group(2).strip()
# Replace initial '>' from subsequent lines
text = text.replace("\n>", "\n")
# Replace with admonition format
return f"!!! {alert_type}\n " + text.replace("\n", "\n ")

return re.sub(CONVERSION_PATTERN, replace_match, content)


def import_object(objname: str) -> Any:
"""Import a python object by its qualified name.

Expand Down Expand Up @@ -90,11 +124,15 @@ def get_classes(*cls_or_modules: str, strict: bool = False) -> Generator[Any, No
####################################################################################################
# Macro functions
####################################################################################################
def class_diagram(
def class_diagram( # noqa: C901 # pylint: disable=too-many-locals
*cls_or_modules: str,
full: bool = False,
strict: bool = False,
namespace: Optional[str] = None,
tree_direction: str = "up",
chart_direction: str = "LR",
highlight_family_base_classes: bool = False,
highlight_device_drivers: bool = False,
) -> str:
"""Create a mermaid classDiagram for the provided classes or modules.

Expand All @@ -104,6 +142,13 @@ def class_diagram(
strict: A boolean indicating to only consider classes that are strictly defined in that
module and not imported from somewhere else.
namespace: Limits the diagram to only include classes defined in this namespace.
tree_direction: A string indicating the direction of traversal in the class hierarchy,
either "up" or "down".
chart_direction: A string indicating the direction of the chart, either
"LR" (left to right), "RL" (right to left),
"TB" (top to bottom), or "BT" (bottom to top).
highlight_family_base_classes: Indicate to highlight the family base classes in cyan.
highlight_device_drivers: Indicate to highlight the device drivers in lightgreen.

Returns:
The mermaid code block with complete syntax for the classDiagram.
Expand All @@ -112,16 +157,38 @@ def class_diagram(
ValueError: If no classDiagram can be created.
"""
inheritances: Set[Tuple[str, str]] = set()
family_base_classes: Set[str] = set()
device_drivers: Set[str] = set()

def get_tree_upwards(cls: Any) -> None:
if getattr(cls, "_product_family_base_class", None) == cls:
family_base_classes.add(cls.__name__)
if abc.ABC not in cls.__bases__:
device_drivers.add(cls.__name__)

def get_tree(cls: Any) -> None:
for base in cls.__bases__:
if base.__name__ == "object":
continue
if namespace and not base.__module__.startswith(namespace):
continue
inheritances.add((base.__name__, cls.__name__))
if full:
get_tree(base)
get_tree_upwards(base)

def get_tree_downwards(cls: Any) -> None:
if getattr(cls, "_product_family_base_class", None) == cls:
family_base_classes.add(cls.__name__)
if abc.ABC not in cls.__bases__:
device_drivers.add(cls.__name__)

for subclass in cls.__subclasses__():
if namespace and not subclass.__module__.startswith(namespace):
continue
inheritances.add((cls.__name__, subclass.__name__))
if full:
get_tree_downwards(subclass)

get_tree = get_tree_upwards if tree_direction == "up" else get_tree_downwards

for cls_item in get_classes(*cls_or_modules, strict=strict):
get_tree(cls_item)
Expand All @@ -130,11 +197,18 @@ def get_tree(cls: Any) -> None:
msg = "No class hierarchy can be created."
raise ValueError(msg)

return (
"```mermaid\nclassDiagram\n"
+ "\n".join(f" {a} <|-- {b}" for a, b in sorted(inheritances))
+ "\n```"
mermaid_code_block = f"```mermaid\nclassDiagram\n direction {chart_direction}\n" + "\n".join(
f" {a} <|-- {b}" for a, b in sorted(inheritances)
)
if highlight_family_base_classes:
for family_base_class in sorted(family_base_classes):
mermaid_code_block += f"\n style {family_base_class} stroke:orangered,stroke-width:4px"
if highlight_device_drivers:
for device_driver in sorted(device_drivers):
mermaid_code_block += f"\n style {device_driver} fill:lightgreen"
mermaid_code_block += "\n```"

return mermaid_code_block


def create_repo_link(link_text: str, base_repo_url: str, relative_repo_path: str) -> str:
Expand Down Expand Up @@ -162,8 +236,7 @@ def define_env(env: MacrosPlugin) -> None:
"""
# Read in the current package version number to use in templates and files
with open(
pathlib.Path(f"{pathlib.Path(__file__).parents[1]}") / "pyproject.toml",
"rb",
pathlib.Path(f"{pathlib.Path(__file__).parents[1]}") / "pyproject.toml", "rb"
) as file_handle:
pyproject_data = tomli.load(file_handle)
package_version = "v" + pyproject_data["tool"]["poetry"]["version"]
Expand All @@ -183,14 +256,16 @@ def define_env(env: MacrosPlugin) -> None:
def on_post_page_macros(env: MacrosPlugin) -> None:
"""Post-process pages."""
# Check if there are any replacements to perform on the page
if env.page.file.src_path in PAGE_REPLACEMENTS: # pyright: ignore[reportUnknownMemberType]
for search, replace in PAGE_REPLACEMENTS[env.page.file.src_path]: # pyright: ignore[reportUnknownMemberType]
env.markdown = env.markdown.replace(search, replace) # pyright: ignore[reportUnknownMemberType]
if env.page.file.src_path in PAGE_REPLACEMENTS:
for search, replace in PAGE_REPLACEMENTS[env.page.file.src_path]:
env.markdown = env.markdown.replace(search, replace)
# Check if all black format disable comments should be removed from the page
if env.page.file.src_path in FILES_TO_REMOVE_BLACK_FORMATTER_DISABLE_COMMENT: # pyright: ignore[reportUnknownMemberType]
env.markdown = env.markdown.replace("# fmt: off\n", "") # pyright: ignore[reportUnknownMemberType]
if env.page.file.src_path in FILES_TO_REMOVE_BLACK_FORMATTER_DISABLE_COMMENT:
env.markdown = env.markdown.replace("# fmt: off\n", "")
# Check if there are any admonitions to replace on the page
env.markdown = convert_gfm_alerts_to_admonitions(env.markdown)
# Check if the title is correct
if actual_title_match := HEADER_ONE_REGEX.search(env.markdown): # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType]
if actual_title_match := HEADER_ONE_REGEX.search(env.markdown):
actual_title = actual_title_match.group(1)
if env.page.title != actual_title: # pyright: ignore[reportUnknownMemberType]
env.page.title = actual_title # pyright: ignore[reportUnknownMemberType]
env.page.title = actual_title # pyright: ignore[reportAttributeAccessIssue]
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ numpy = [
{python = "^3.8, <3.12", version = "^1.24"}
]
protobuf = "^5.28.1"
python = "^3.8"
python = "^3.8, <3.13"
python-dateutil = "^2.8.2"
tm_data_types = "^0.1.0"
typing_extensions = "^4.12.0"
Expand Down
Loading