Source code for stouputils.applications.automatic_docs.common
""" Common utilities shared by documentation generators (Sphinx, Zensical, etc.).
This module contains functions and helpers that are used by multiple documentation
backends, avoiding code duplication.
"""
# Imports
import os
from collections import defaultdict
from collections.abc import Callable
from ...config import StouputilsConfig as Cfg
from ...continuous_delivery import version_to_float
from ...decorators import simple_cache
from ...io.path import super_open
from ...print.message import info
# Functions
[docs]
def check_base_dependencies() -> None:
""" Check for each auto-docs requirement if it is installed.
Raises:
ImportError: If any requirement from ``Cfg.AUTO_DOCS_REQUIREMENTS`` is not installed
"""
import importlib
for requirement in Cfg.AUTO_DOCS_REQUIREMENTS:
try:
importlib.import_module(requirement)
except ImportError as e:
requirements_str: str = " ".join(Cfg.AUTO_DOCS_REQUIREMENTS)
raise ImportError(f"{requirement} is not installed. Please install the following requirements to use automatic_docs: '{requirements_str}'") from e
[docs]
def download_asset(url: str, target_path: str) -> None:
""" Download a file from a URL to a local path.
Args:
url (str): URL to download from
target_path (str): Local file path to save to
"""
import requests
response = requests.get(url, timeout=30)
response.raise_for_status()
os.makedirs(os.path.dirname(target_path), exist_ok=True)
with open(target_path, "wb") as f:
f.write(response.content)
[docs]
@simple_cache
def get_versions_from_github(github_user: str, github_repo: str, recent_minor_versions: int = 2) -> list[str]:
""" Get list of versions from GitHub gh-pages branch.
Only shows detailed versions for the last N minor versions, and keeps only
the latest patch version for older minor versions.
Args:
github_user (str): GitHub username
github_repo (str): GitHub repository name
recent_minor_versions (int): Number of recent minor versions to show all patches for (-1 for all).
Returns:
list[str]: List of versions, with 'latest' as first element
"""
import requests
version_list: list[str] = []
try:
response = requests.get(f"https://api.github.com/repos/{github_user}/{github_repo}/contents?ref=gh-pages")
if response.status_code == 200:
contents: list[dict[str, str]] = response.json()
all_versions: list[str] = sorted([
d["name"].replace("v", "")
for d in contents
if d["type"] == "dir" and d["name"].startswith("v")
], key=version_to_float, reverse=True
)
info(f"Found versions from GitHub: {all_versions}")
# Group versions by major.minor
minor_versions: dict[str, list[str]] = defaultdict(list)
for version in all_versions:
parts = version.split(".")
if len(parts) >= 2:
minor_key = f"{parts[0]}.{parts[1]}"
minor_versions[minor_key].append(version)
info(f"Grouped minor versions: {dict(minor_versions)}")
# Get the sorted minor version keys
sorted_minors = sorted(minor_versions.keys(), key=version_to_float, reverse=True)
info(f"Sorted minor versions: {sorted_minors}")
# Build final version list
final_versions: list[str] = []
for i, minor_key in enumerate(sorted_minors):
if recent_minor_versions == -1 or i < recent_minor_versions:
# Keep all patch versions for the recent minor versions
final_versions.extend(minor_versions[minor_key])
else:
# Keep only the latest patch version for older minor versions
final_versions.append(minor_versions[minor_key][0])
version_list = ["latest", *final_versions]
except Exception as e:
info(f"Failed to get versions from GitHub: {e}")
version_list = ["latest"]
return version_list
[docs]
def generate_version_selector(
github_user: str,
github_repo: str,
get_versions_function: Callable[[str, str, int], list[str]] = get_versions_from_github,
recent_minor_versions: int = 2,
) -> str:
""" Generate the HTML version selector string from GitHub versions.
Args:
github_user (str): GitHub username
github_repo (str): GitHub repository name
get_versions_function (Callable[[str, str, int], list[str]]): Function to get versions from GitHub
recent_minor_versions (int): Number of recent minor versions to show all patches for. Defaults to 2
Returns:
str: Markdown string with version links (e.g. ``**Versions**: latest, v1.0.0, ...``)
"""
version_list: list[str] = get_versions_function(github_user, github_repo, recent_minor_versions)
version_links: list[str] = []
for version in version_list:
if version == "latest":
version_links.append('<a href="../latest/">latest</a>')
else:
version_links.append(f'<a href="../v{version}/">v{version}</a>')
return "\n\n**Versions**: " + ", ".join(version_links)
[docs]
def generate_redirect_html(filepath: str) -> None:
""" Generate HTML content for redirect page.
Args:
filepath (str): Path to the file where the HTML content should be written
"""
with super_open(filepath, "w", encoding="utf-8") as f:
f.write("""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="refresh" content="0;url=./latest/">
<title>Redirecting...</title>
</head>
<body>
<p>If you are not redirected automatically, <a href="./latest/">click here</a>.</p>
</body>
</html>
""")