""" MkDocsRenderer Generates Markdown source files compatible with MkDocs Material and mkdocstrings, ensuring: - Root index.md always exists - Parent package indexes are created automatically - Child modules are linked in parent index files """ from pathlib import Path from docforge.models import Project class MkDocsRenderer: """ Renderer that generates Markdown source files formatted for the MkDocs 'mkdocstrings' plugin. """ name = "mkdocs" # ------------------------- # Public API # ------------------------- def generate_sources(self, project: Project, out_dir: Path) -> None: """ Produce a set of Markdown files in the output directory based on the provided Project models. Args: project: The project models to render. out_dir: Target directory for documentation files. """ out_dir.mkdir(parents=True, exist_ok=True) self._ensure_root_index(project, out_dir) modules = list(project.get_all_modules()) paths = {m.path for m in modules} # Package detection (level-agnostic) packages = { p for p in paths if any(other.startswith(p + ".") for other in paths) } for module in modules: self._write_module(module, packages, out_dir) # ------------------------- # Internal helpers # ------------------------- def _write_module(self, module, packages: set[str], out_dir: Path) -> None: """ Write a single module's documentation file. Packages are written as 'index.md' inside their respective directories. Args: module: The module to write. packages: A set of module paths that are identified as packages. out_dir: The base output directory. """ parts = module.path.split(".") if module.path in packages: # Package → directory/index.md dir_path = out_dir.joinpath(*parts) dir_path.mkdir(parents=True, exist_ok=True) md_path = dir_path / "index.md" link_target = f"{parts[-1]}/" else: # Leaf module → parent_dir/.md dir_path = out_dir.joinpath(*parts[:-1]) dir_path.mkdir(parents=True, exist_ok=True) md_path = dir_path / f"{parts[-1]}.md" link_target = f"{parts[-1]}.md" title = parts[-1].replace("_", " ").title() content = self._render_markdown(title, module.path) if not md_path.exists() or md_path.read_text(encoding="utf-8") != content: md_path.write_text(content, encoding="utf-8") self._ensure_parent_index(parts, out_dir, link_target, title) def _render_markdown(self, title: str, module_path: str) -> str: """ Generate the Markdown content for a module file. Args: title: The display title for the page. module_path: The dotted path of the module to document. Returns: A string containing the Markdown source. """ return ( f"# {title}\n\n" f"::: {module_path}\n" ) def _ensure_root_index( self, project: Project, out_dir: Path ) -> None: root_index = out_dir / "index.md" if not root_index.exists(): root_index.write_text( f"# {project.name}\n\n" "## Modules\n\n", encoding="utf-8", ) def _ensure_parent_index( self, parts: list[str], out_dir: Path, link_target: str, title: str, ) -> None: if len(parts) == 1: parent_index = out_dir / "index.md" link = f"- [{title}]({link_target})\n" else: parent_dir = out_dir.joinpath(*parts[:-1]) parent_dir.mkdir(parents=True, exist_ok=True) parent_index = parent_dir / "index.md" link = f"- [{title}]({link_target})\n" if not parent_index.exists(): parent_title = parts[-2].replace("_", " ").title() parent_index.write_text( f"# {parent_title}\n\n", encoding="utf-8", ) content = parent_index.read_text(encoding="utf-8") if link not in content: parent_index.write_text(content + link, encoding="utf-8")