Source code for utils.generate_rst

"""
generate_rst.py

A utility module for generating reStructuredText (.rst) files for Python modules
to be used with Sphinx documentation.

This script scans a given directory for Python modules (excluding specified directories)
and automatically creates corresponding .rst files using the `.. automodule::` directive.
These files are intended to be included in a Sphinx documentation build.

Usage:
    Run this script as a standalone module to generate .rst files:

        $ export PYTHONPATH=$PYTHONPATH:.
        $ python utils/generate_rst.py

    This will:
        - Search for all `.py` files (excluding `__init__.py`) in the specified source directory.
        - Skip excluded directories like `tests`, `build`, and `__pycache__`.
        - Output `.rst` files to the appropriate subdirectories in `source/`.

Functions:
    - generate_module_rst(module_path, output_dir):
        Generates a .rst file for a single module using the Sphinx automodule directive.

    - find_modules(root_dir, current_dir, exclude_dirs=None):
        Recursively finds Python modules in a directory, excluding specified directories.

Note:
    The generated .rst files are intended to be included in the Sphinx build.
"""

import os
from pathlib import Path

from utils.file_ops import get_git_repo_name, get_git_root

PROJECT_NAME = get_git_repo_name()

# List of subdirectory names to exclude from Sphinx documentation and sys.path
EXCLUDED_DIRS = [
    ".git",
    ".github",
    ".idea",
    ".ipynb_checkpoints",
    ".pytest_cache",
    ".venv",
    ".virtual_documents",
    ".vscode",
    f"{PROJECT_NAME}.egg-info",
    "__pycache__",
    "build",
    "docs",
    "environments",
    "examples",
    "input",
    "img",
    "js",
    "output",
    "tests",
    "third",
    "source/_static",
    "source/_templates",
    "source/reports",
]


[docs] def generate_module_rst(module_path: str, output_dir: str) -> None: """Generates a reStructuredText file for a Python module. Args: module_path (str): The relative path to the Python module (e.g., 'utils/file_ops'). output_dir (str): The directory where the .rst file will be created. """ os.makedirs(output_dir, exist_ok=True) module_name = os.path.basename(module_path) # The automodule directive needs the importable name. # If it's in a subdirectory like utils/, it should be 'utils.file_ops' import_path = module_path.replace(os.sep, ".") filename = os.path.join(output_dir, f"{module_name}.rst") escape_character_module_name = module_name.replace("_", "\\_") with open(filename, "w") as f: f.write(f"{escape_character_module_name} Module\n") f.write("=" * (len(escape_character_module_name) + 7) + "\n\n") f.write(f".. automodule:: {import_path}\n") f.write(" :members:\n") f.write(" :undoc-members:\n") f.write(" :show-inheritance:\n") print(f"Generated: {filename}")
[docs] def find_modules( root_dir: str, current_dir: str, exclude_dirs: list[str] | None = None ) -> list[str]: """Recursively finds Python modules (.py files) within a given root directory. Args: root_dir (str): The root directory of the project. current_dir (str): The current directory to search. exclude_dirs (list, optional): A list of directory names to exclude. Defaults to None. Returns: list: A list of module relative paths (e.g., ['utils/file_ops']). """ modules = [] if exclude_dirs is None: exclude_dirs = [] for item in os.listdir(current_dir): item_path = os.path.join(current_dir, item) rel_item_path = os.path.relpath(item_path, root_dir) if os.path.isdir(item_path): if item in exclude_dirs or rel_item_path in exclude_dirs: continue modules.extend(find_modules(root_dir, item_path, exclude_dirs)) elif item.endswith(".py") and item != "__init__.py": relative_path = os.path.relpath(item_path, root_dir) modules.append(relative_path[:-3]) # Remove .py return modules
[docs] def run_generator(git_root: str | None, excluded_directories: list[str]) -> None: """Recursively finds Python modules and generates corresponding .rst files. Args: git_root (str): The root directory of the project. excluded_directories (list): A list of directory names to exclude. """ if git_root is None: print("No git root found, cannot generate documentation.") return # Find all modules and packages modules_to_document = find_modules(git_root, git_root, excluded_directories) if not modules_to_document: print(f"No Python modules or packages found in: {git_root}") else: for module_rel_path in sorted(modules_to_document): # Determine output directory based on module location parts = Path(module_rel_path).parts if module_rel_path in [ os.path.join("source", "conf"), ]: output_subdir = os.path.join(git_root, "source", "reference") # Adjust module path for these module_rel_path = os.path.basename(module_rel_path) elif parts[0] == "utils": output_subdir = os.path.join(git_root, "source", "utils") else: # Default to api/ for others output_subdir = os.path.join(git_root, "source", "api") generate_module_rst(module_rel_path, output_subdir) print("\nDocumentation generation complete.")
if __name__ == "__main__": run_generator(get_git_root(), EXCLUDED_DIRS)