This commit is contained in:
75
src/docgen/generators/html.py
Normal file
75
src/docgen/generators/html.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
"""HTML documentation generator."""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
|
from docgen.models import DocConfig, Endpoint
|
||||||
|
from docgen.generators import BaseGenerator
|
||||||
|
|
||||||
|
|
||||||
|
def slugify(text: str) -> str:
|
||||||
|
"""Convert text to URL-friendly slug."""
|
||||||
|
return re.sub(r'[^a-z0-9]+', '-', text.lower()).strip('-')
|
||||||
|
|
||||||
|
|
||||||
|
class HTMLGenerator(BaseGenerator):
|
||||||
|
"""Generator for Stripe-like interactive HTML documentation."""
|
||||||
|
|
||||||
|
def __init__(self, config: DocConfig = None):
|
||||||
|
super().__init__(config)
|
||||||
|
template_dir = Path(__file__).parent.parent / "templates"
|
||||||
|
self.env = Environment(
|
||||||
|
loader=FileSystemLoader(template_dir),
|
||||||
|
autoescape=select_autoescape(["html", "xml"]),
|
||||||
|
)
|
||||||
|
self.env.filters["slugify"] = slugify
|
||||||
|
|
||||||
|
def generate(self, endpoints: list[Endpoint], output_dir: Path) -> Path:
|
||||||
|
"""Generate HTML documentation."""
|
||||||
|
output_dir = self._ensure_output_dir(output_dir)
|
||||||
|
|
||||||
|
grouped = self._group_endpoints(endpoints)
|
||||||
|
base_template = self.env.get_template("base.html.jinja2")
|
||||||
|
sidebar_template = self.env.get_template("sidebar.html.jinja2")
|
||||||
|
|
||||||
|
rendered_content = base_template.render(
|
||||||
|
title=self.config.title,
|
||||||
|
description=self.config.description,
|
||||||
|
version=self.config.version,
|
||||||
|
endpoints=endpoints,
|
||||||
|
grouped_endpoints=grouped,
|
||||||
|
sidebar=sidebar_template.render(grouped_endpoints=grouped),
|
||||||
|
theme=self.config.theme,
|
||||||
|
)
|
||||||
|
|
||||||
|
index_path = output_dir / "index.html"
|
||||||
|
index_path.write_text(rendered_content)
|
||||||
|
|
||||||
|
self._copy_static_files(output_dir)
|
||||||
|
|
||||||
|
return index_path
|
||||||
|
|
||||||
|
def _group_endpoints(self, endpoints: list[Endpoint]) -> dict[str, list[Endpoint]]:
|
||||||
|
"""Group endpoints by tags or path prefixes."""
|
||||||
|
grouped = {}
|
||||||
|
for endpoint in endpoints:
|
||||||
|
if endpoint.tags:
|
||||||
|
tag = endpoint.tags[0] if endpoint.tags else "Other"
|
||||||
|
else:
|
||||||
|
parts = endpoint.path.strip("/").split("/")
|
||||||
|
tag = parts[0] if parts else "Other"
|
||||||
|
|
||||||
|
if tag not in grouped:
|
||||||
|
grouped[tag] = []
|
||||||
|
grouped[tag].append(endpoint)
|
||||||
|
return dict(sorted(grouped.items()))
|
||||||
|
|
||||||
|
def _copy_static_files(self, output_dir: Path) -> None:
|
||||||
|
"""Copy static CSS and JS files to output directory."""
|
||||||
|
static_dir = Path(__file__).parent.parent / "static"
|
||||||
|
output_static = output_dir / "static"
|
||||||
|
output_static.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
for file in static_dir.iterdir():
|
||||||
|
if file.is_file():
|
||||||
|
(output_static / file.name).write_text(file.read_text())
|
||||||
Reference in New Issue
Block a user