rendered MkDocs
This commit is contained in:
@@ -7,9 +7,11 @@ until their contracts are finalized.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .loader import GriffeLoader
|
from .loader import GriffeLoader
|
||||||
|
from .renderers import MkDocsRenderer
|
||||||
from . import model
|
from . import model
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"GriffeLoader",
|
"GriffeLoader",
|
||||||
|
"MkDocsRenderer",
|
||||||
"model",
|
"model",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from .loader import GriffeLoader
|
from .loader import GriffeLoader
|
||||||
|
from .renderers import MkDocsRenderer
|
||||||
from . import model
|
from . import model
|
||||||
|
|
||||||
__all__: list[str]
|
__all__: list[str]
|
||||||
|
|||||||
5
docforge/renderers/__init__.py
Normal file
5
docforge/renderers/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from docforge.renderers.mkdocs import MkDocsRenderer
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"MkDocsRenderer",
|
||||||
|
]
|
||||||
3
docforge/renderers/__init__.pyi
Normal file
3
docforge/renderers/__init__.pyi
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from docforge.renderers.mkdocs import MkDocsRenderer
|
||||||
|
|
||||||
|
__all__ = ["MkDocsRenderer"]
|
||||||
13
docforge/renderers/base.py
Normal file
13
docforge/renderers/base.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from docforge.model import Project
|
||||||
|
|
||||||
|
|
||||||
|
class RendererConfig:
|
||||||
|
"""Renderer configuration container."""
|
||||||
|
|
||||||
|
def __init__(self, out_dir: Path, project: Project) -> None:
|
||||||
|
self.out_dir = out_dir
|
||||||
|
self.project = project
|
||||||
26
docforge/renderers/base.pyi
Normal file
26
docforge/renderers/base.pyi
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
from docforge.model import Project
|
||||||
|
|
||||||
|
|
||||||
|
class RendererConfig:
|
||||||
|
"""Renderer configuration container."""
|
||||||
|
|
||||||
|
out_dir: Path
|
||||||
|
project: Project
|
||||||
|
|
||||||
|
def __init__(self, out_dir: Path, project: Project) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
class DocRenderer(Protocol):
|
||||||
|
"""Renderer interface."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def generate_sources(
|
||||||
|
self,
|
||||||
|
project: Project,
|
||||||
|
out_dir: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Generate renderer-specific source files."""
|
||||||
58
docforge/renderers/mkdocs.py
Normal file
58
docforge/renderers/mkdocs.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from docforge.model import Project
|
||||||
|
|
||||||
|
|
||||||
|
class MkDocsRenderer:
|
||||||
|
"""MkDocs source generator using mkdocstrings."""
|
||||||
|
|
||||||
|
name = "mkdocs"
|
||||||
|
|
||||||
|
def generate_sources(self, project: Project, out_dir: Path) -> None:
|
||||||
|
"""
|
||||||
|
Generate Markdown files with mkdocstrings directives.
|
||||||
|
|
||||||
|
Structure rules:
|
||||||
|
- Each top-level package gets a directory
|
||||||
|
- Modules become .md files
|
||||||
|
- Packages (__init__) become index.md
|
||||||
|
"""
|
||||||
|
for module in project.get_all_modules():
|
||||||
|
self._write_module(project, module, out_dir)
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# Internal helpers
|
||||||
|
# -------------------------
|
||||||
|
|
||||||
|
def _write_module(self, project: Project, module, out_dir: Path) -> None:
|
||||||
|
parts = module.path.split(".")
|
||||||
|
|
||||||
|
# Root package directory
|
||||||
|
pkg_dir = out_dir / parts[0]
|
||||||
|
pkg_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Package (__init__.py) → index.md
|
||||||
|
if module.path == parts[0]:
|
||||||
|
md_path = pkg_dir / "index.md"
|
||||||
|
title = parts[0].replace("_", " ").title()
|
||||||
|
else:
|
||||||
|
# Submodule → <name>.md
|
||||||
|
md_path = pkg_dir / f"{parts[-1]}.md"
|
||||||
|
title = parts[-1].replace("_", " ").title()
|
||||||
|
|
||||||
|
content = self._render_markdown(title, module.path)
|
||||||
|
|
||||||
|
# Idempotent write
|
||||||
|
if md_path.exists():
|
||||||
|
if md_path.read_text(encoding="utf-8") == content:
|
||||||
|
return
|
||||||
|
|
||||||
|
md_path.write_text(content, encoding="utf-8")
|
||||||
|
|
||||||
|
def _render_markdown(self, title: str, module_path: str) -> str:
|
||||||
|
return (
|
||||||
|
f"# {title}\n\n"
|
||||||
|
f"::: {module_path}\n"
|
||||||
|
)
|
||||||
17
docforge/renderers/mkdocs.pyi
Normal file
17
docforge/renderers/mkdocs.pyi
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from docforge.model import Project
|
||||||
|
from docforge.renderers.base import DocRenderer
|
||||||
|
|
||||||
|
|
||||||
|
class MkDocsRenderer:
|
||||||
|
"""MkDocs source generator using mkdocstrings."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def generate_sources(
|
||||||
|
self,
|
||||||
|
project: Project,
|
||||||
|
out_dir: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Generate Markdown files with mkdocstrings directives."""
|
||||||
0
tests/renderers/__init__.py
Normal file
0
tests/renderers/__init__.py
Normal file
19
tests/renderers/test_mkdocs_content.py
Normal file
19
tests/renderers/test_mkdocs_content.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from docforge import MkDocsRenderer
|
||||||
|
from docforge.model import Project, Module
|
||||||
|
|
||||||
|
|
||||||
|
def test_mkdocs_file_content(tmp_path: Path):
|
||||||
|
project = Project("testpkg")
|
||||||
|
project.add_module(Module("testpkg.mod"))
|
||||||
|
|
||||||
|
out_dir = tmp_path / "docs"
|
||||||
|
renderer = MkDocsRenderer()
|
||||||
|
|
||||||
|
renderer.generate_sources(project, out_dir)
|
||||||
|
|
||||||
|
content = (out_dir / "testpkg" / "mod.md").read_text()
|
||||||
|
|
||||||
|
assert "# Mod" in content
|
||||||
|
assert "::: testpkg.mod" in content
|
||||||
20
tests/renderers/test_mkdocs_idempotency.py
Normal file
20
tests/renderers/test_mkdocs_idempotency.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from docforge import MkDocsRenderer
|
||||||
|
from docforge.model import Project, Module
|
||||||
|
|
||||||
|
|
||||||
|
def test_mkdocs_idempotent(tmp_path: Path):
|
||||||
|
project = Project("testpkg")
|
||||||
|
project.add_module(Module("testpkg.mod"))
|
||||||
|
|
||||||
|
out_dir = tmp_path / "docs"
|
||||||
|
renderer = MkDocsRenderer()
|
||||||
|
|
||||||
|
renderer.generate_sources(project, out_dir)
|
||||||
|
first = (out_dir / "testpkg" / "mod.md").read_text()
|
||||||
|
|
||||||
|
renderer.generate_sources(project, out_dir)
|
||||||
|
second = (out_dir / "testpkg" / "mod.md").read_text()
|
||||||
|
|
||||||
|
assert first == second
|
||||||
18
tests/renderers/test_mkdocs_structure.py
Normal file
18
tests/renderers/test_mkdocs_structure.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from docforge import MkDocsRenderer
|
||||||
|
from docforge.model import Project, Module
|
||||||
|
|
||||||
|
|
||||||
|
def test_mkdocs_directory_structure(tmp_path: Path):
|
||||||
|
project = Project("testpkg")
|
||||||
|
project.add_module(Module("testpkg"))
|
||||||
|
project.add_module(Module("testpkg.sub"))
|
||||||
|
|
||||||
|
out_dir = tmp_path / "docs"
|
||||||
|
renderer = MkDocsRenderer()
|
||||||
|
|
||||||
|
renderer.generate_sources(project, out_dir)
|
||||||
|
|
||||||
|
assert (out_dir / "testpkg" / "index.md").exists()
|
||||||
|
assert (out_dir / "testpkg" / "sub.md").exists()
|
||||||
Reference in New Issue
Block a user