Source code for chimcla.util_doc

"""
Module to prepare and execute the generation of documentation (via sphinx).

used by some `chimcla ...` commands.
"""


import os
import subprocess
import time
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from ipydex import IPS, activate_ips_on_exception
activate_ips_on_exception()

FILE_NAME = os.path.split(os.path.abspath(__file__))[1]


# created by llama-4-Scout (+ manual adaptions)
[docs] def generate_module_docs(package_path: str|None = None, output_dir: str|None = None): if package_path is None: package_path = os.path.abspath(os.path.dirname(__file__)) package_name = "chimcla" assert os.path.isdir(package_path) assert os.path.isfile(Path(package_path)/"__init__.py") if output_dir is None: repo_root = os.path.dirname(os.path.dirname(package_path)) output_dir = Path(repo_root)/"docs"/"source" module_dir = output_dir/"modules" import pkgutil import importlib index_path = os.path.join(output_dir, 'api_links.md') os.makedirs(module_dir, exist_ok=True) with open(index_path, 'w') as index_file: index_file.write(f'# Module Documentation\n\n(autogenerated by `{FILE_NAME}`)\n\n') for module_info in pkgutil.walk_packages([package_path]): module_name = module_info.name # Extract the actual module docstring try: full_module_name = f"{package_name}.{module_name}" module = importlib.import_module(full_module_name) module_docstring = module.__doc__ or "[empty]" except Exception: module_docstring = "[could not import]" # quoting prefix qq = f"{' '*8}> " number_of_lines = get_number_of_lines(package_path, module_name) wrapped_quoted_module_docstring = get_wrapped_quoted_docstring(module_docstring, qq) index_file.write(f"- [{module_name}](apidocs/{package_name}/{package_name}.{module_name}.md)\n") index_file.write(f" - Lines: {number_of_lines}\n") index_file.write(f" - Docstring:{wrapped_quoted_module_docstring}\n") # print(f"File created: {module_path}") print(f"File created: {index_path}")
[docs] def get_number_of_lines(package_path, module_name): try: module_file_path = os.path.join(package_path, f"{module_name}.py") if os.path.isfile(module_file_path): with open(module_file_path, 'r', encoding='utf-8') as fp: number_of_lines = sum(1 for _ in fp) else: number_of_lines = "??" except Exception: number_of_lines = "??" return number_of_lines
[docs] def get_wrapped_quoted_docstring(module_docstring, qq): quoted_module_docstring = f"\n{module_docstring}".replace("\n", f"\n{qq}") wrapped_quoted_module_docstring = f"\n{qq}{quoted_module_docstring}\n{qq}" wrapped_quoted_module_docstring = "\n".join( [elt.rstrip() for elt in wrapped_quoted_module_docstring.split("\n")] ) return wrapped_quoted_module_docstring
[docs] def make_html_doc(docfile=None): if docfile is not None: subprocess.run(['sphinx-build', '-b', 'html', 'docs/source', 'docs/build', docfile]) else: subprocess.run(['sphinx-build', '-b', 'html', 'docs/source', 'docs/build'])
[docs] class DocumentationGenerator(FileSystemEventHandler):
[docs] def on_modified(self, event): if event.src_path.endswith('.py'): make_html_doc()
[docs] def continuously_build_docs(): """ Watch file system changes (by polling) and trigger build on changes. Use CTRL-C to cancel. """ event_handler = DocumentationGenerator() observer = Observer() observer.schedule(event_handler, path='.', recursive=True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join()