ez_docker/scripts/generate_docs.py
2025-01-20 18:13:02 +01:00

218 lines
5.5 KiB
Python
Executable file

#!/bin/env python3
# Copyright: (c) 2025, Luca Bilke <luca@bil.ke>
# MIT License (see LICENSE)
# ruff: noqa: T201
from __future__ import annotations
import sys
import textwrap
from pathlib import Path
from typing import TYPE_CHECKING, cast
import yaml
from ansible_collections.snailed.ez_docker.plugins.module_utils import (
label,
service,
)
if TYPE_CHECKING:
from types import ModuleType
GALAXY_METADATA_PATH = Path("../galaxy.yml")
def get_modules(parent_module: ModuleType) -> list[tuple[str, ModuleType]]:
return [
(name, getattr(parent_module, name)) for name in parent_module.__all__ if name != "common"
]
def settings_format(text: str) -> str:
return "\n".join(
line for line in text.splitlines() if not line.strip().startswith(("default:", "required:"))
)
def get_module_docs(
module: ModuleType,
) -> str:
ret: str = ""
if docs := getattr(module, "DOCUMENTATION", None):
ret = docs
if ret:
return ret
msg = f"Module {module.__name__} has no documentation"
raise ValueError(msg)
def get_metadata() -> str:
with GALAXY_METADATA_PATH.open("r") as f:
metadata = yaml.safe_load(f)
docs = """
module: compose
version_added: 1.0.0
short_description: Simplify docker-compose deployments.
description: Easily create docker-compose files using a single module
seealso:
- name: Compose file reference
description: Complete reference of the compose file spec.
link: https://docs.docker.com/reference/compose-file/
attributes:
check_mode:
support: full
diff_mode:
support: full
"""
if author := metadata.get("author"):
if isinstance(author, str):
docs += f"author: {author}\n"
elif isinstance(author, list):
docs += "author:\n"
for a in cast(list[str], author):
docs += f" - {a}\n"
return docs
def get_settings_docs() -> str:
service_default_definitions = """
service_default_definitions:
description:
- Default definitions for each service
type: dict
suboptions:
"""
service_default_args = """
service_default_args:
description:
- Default arguments for each service helper.
type: dict
suboptions:
"""
label_default_args = """
label_default_args:
description:
- Default arguments for each label helper.
type: dict
suboptions:
"""
for name, module in get_modules(service):
if module.__name__.endswith("common"):
continue
service_default_definitions += f"""
{name}:
description:
- Default definitions for {name} services.
type: dict
"""
service_default_args += textwrap.indent(settings_format(get_module_docs(module)), " " * 8)
if module.__name__.endswith("custom"):
for _, label_module in get_modules(label):
if label_module.__name__.endswith("common"):
continue
service_default_args += textwrap.indent(
settings_format(get_module_docs(label_module)), " " * 24
)
else:
service_default_args += textwrap.indent(
settings_format(service.common.BASE_DOCUMENTATION), " " * 16
)
for _, module in get_modules(label):
if module.__name__.endswith("common"):
continue
label_default_args += textwrap.indent(settings_format(get_module_docs(module)), " " * 8)
return f"""
settings:
description:
- Settings/Defaults for the module.
type: dict
suboptions:
external_networks:
description:
- Networks to mark as external.
type: list
elements: str
external_volumes:
description:
- Volumes to mark as external.
type: list
elements: str
default_definition:
description:
- Default definition for all containers.
- Overwritten by per-service defaults.
type: dict
{textwrap.indent(service_default_definitions, " " * 8)}
{textwrap.indent(service_default_args, " " * 8)}
{textwrap.indent(label_default_args, " " * 8)}
"""
def get_service_docs() -> str:
service_docs = """
services:
description:
- Services to create in the project.
type: dict
suboptions:
"""
for _, module in get_modules(service):
service_docs += f"""
{textwrap.indent(get_module_docs(module), " " * 8)}
"""
if module.__name__.endswith("custom"):
for _, label_module in get_modules(label):
if label_module.__name__ == "common":
continue
service_docs += textwrap.indent(
settings_format(get_module_docs(label_module)), " " * 24
)
else:
service_docs += textwrap.indent(service.common.BASE_DOCUMENTATION, " " * 16)
return service_docs
ret = f"""
---
{get_metadata()}
options:
name:
description:
- Name of the compose project to create or modify.
aliases: [project]
type: str
project_dir:
description:
- Path to store project directory under.
type: path
{textwrap.indent(get_settings_docs(), " " * 4)}
{textwrap.indent(get_service_docs(), " " * 4)}
"""
ret = "\n".join(line for line in ret.splitlines() if line.strip())
print(ret)
try:
yaml.safe_load(ret)
except yaml.YAMLError as e:
print("YAML is invalid!\n", file=sys.stderr)
print(e, file=sys.stderr)