217 lines
6.8 KiB
Python
217 lines
6.8 KiB
Python
"""Base renderer interface for doc-forge output generation.
|
|
|
|
The renderer system provides a pluggable architecture for generating
|
|
different output formats from the same documentation model. All renderers
|
|
implement the DocRenderer protocol, ensuring consistent behavior across
|
|
different output formats.
|
|
|
|
This module defines the base interface and configuration that all
|
|
renderers must follow.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Protocol, runtime_checkable
|
|
|
|
from docforge.model import Project
|
|
|
|
|
|
@runtime_checkable
|
|
class DocRenderer(Protocol):
|
|
"""Protocol for documentation renderers.
|
|
|
|
DocRenderer defines the interface that all documentation renderers
|
|
must implement. This protocol ensures that renderers can be used
|
|
interchangeably while providing consistent behavior.
|
|
|
|
All renderers must:
|
|
1. Have a unique name identifier
|
|
2. Generate source files from the documentation model
|
|
3. Support building final artifacts
|
|
4. Optionally support serving documentation locally
|
|
|
|
Attributes:
|
|
name: Unique identifier for the renderer
|
|
"""
|
|
|
|
name: str
|
|
|
|
def generate_sources(self, project: Project, out_dir: Path) -> None:
|
|
"""Generate renderer-specific source files.
|
|
|
|
This method converts the documentation model into renderer-specific
|
|
source files (e.g., Markdown for MkDocs, reStructuredText for Sphinx).
|
|
The generated files are written to the specified output directory.
|
|
|
|
Args:
|
|
project: The documentation project to render
|
|
out_dir: Directory where source files should be written
|
|
"""
|
|
...
|
|
|
|
def build(self, config: 'RendererConfig') -> None:
|
|
"""Build final documentation artifacts.
|
|
|
|
This method takes the generated source files and builds the final
|
|
documentation artifacts (e.g., HTML site, PDF, etc.). The specific
|
|
output depends on the renderer type.
|
|
|
|
Args:
|
|
config: Configuration for the build process
|
|
"""
|
|
...
|
|
|
|
def serve(self, config: 'RendererConfig') -> None:
|
|
"""Serve documentation locally (optional).
|
|
|
|
This method starts a local development server to serve the
|
|
documentation. This is optional and not all renderers support
|
|
serving functionality.
|
|
|
|
Args:
|
|
config: Configuration for the serve process
|
|
|
|
Raises:
|
|
NotImplementedError: If serving is not supported
|
|
"""
|
|
...
|
|
|
|
|
|
class RendererConfig:
|
|
"""Base configuration for renderers.
|
|
|
|
RendererConfig provides common configuration options that are
|
|
applicable to all renderers. Each renderer can extend this class
|
|
to add renderer-specific configuration options.
|
|
|
|
Attributes:
|
|
out_dir: Output directory for generated files
|
|
project: The documentation project being rendered
|
|
extra: Additional renderer-specific configuration
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
out_dir: Path,
|
|
project: Project,
|
|
extra: Optional[Dict[str, Any]] = None,
|
|
) -> None:
|
|
"""Initialize renderer configuration.
|
|
|
|
Args:
|
|
out_dir: Output directory for generated files
|
|
project: The documentation project being rendered
|
|
extra: Additional renderer-specific configuration options
|
|
"""
|
|
self.out_dir: Path = out_dir
|
|
self.project: Project = project
|
|
self.extra: Dict[str, Any] = extra or {}
|
|
|
|
def get_extra(self, key: str, default: Any = None) -> Any:
|
|
"""Get an extra configuration value.
|
|
|
|
Args:
|
|
key: The configuration key to retrieve
|
|
default: Default value if key is not found
|
|
|
|
Returns:
|
|
The configuration value or default
|
|
"""
|
|
return self.extra.get(key, default)
|
|
|
|
def set_extra(self, key: str, value: Any) -> None:
|
|
"""Set an extra configuration value.
|
|
|
|
Args:
|
|
key: The configuration key to set
|
|
value: The value to set
|
|
"""
|
|
self.extra[key] = value
|
|
|
|
|
|
class BaseRenderer(ABC):
|
|
"""Abstract base class for renderers.
|
|
|
|
BaseRenderer provides a common foundation for all renderer implementations.
|
|
It implements shared functionality and defines the abstract methods that
|
|
concrete renderers must implement.
|
|
|
|
This class helps ensure consistent behavior across different renderer
|
|
implementations while reducing code duplication.
|
|
|
|
Attributes:
|
|
name: Unique identifier for the renderer
|
|
"""
|
|
|
|
name: str
|
|
|
|
def __init__(self, name: str) -> None:
|
|
"""Initialize the base renderer.
|
|
|
|
Args:
|
|
name: Unique identifier for the renderer
|
|
"""
|
|
self.name = name
|
|
|
|
@abstractmethod
|
|
def generate_sources(self, project: Project, out_dir: Path) -> None:
|
|
"""Generate renderer-specific source files.
|
|
|
|
Args:
|
|
project: The documentation project to render
|
|
out_dir: Directory where source files should be written
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def build(self, config: RendererConfig) -> None:
|
|
"""Build final documentation artifacts.
|
|
|
|
Args:
|
|
config: Configuration for the build process
|
|
"""
|
|
pass
|
|
|
|
def serve(self, config: RendererConfig) -> None:
|
|
"""Serve documentation locally.
|
|
|
|
Default implementation raises NotImplementedError. Renderers that
|
|
support serving should override this method.
|
|
|
|
Args:
|
|
config: Configuration for the serve process
|
|
|
|
Raises:
|
|
NotImplementedError: If serving is not supported
|
|
"""
|
|
raise NotImplementedError(f"Serving is not supported by {self.name} renderer")
|
|
|
|
def ensure_output_dir(self, out_dir: Path) -> None:
|
|
"""Ensure the output directory exists.
|
|
|
|
Args:
|
|
out_dir: Directory to ensure exists
|
|
"""
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
def validate_project(self, project: Project) -> None:
|
|
"""Validate that the project is suitable for rendering.
|
|
|
|
Args:
|
|
project: The project to validate
|
|
|
|
Raises:
|
|
ValueError: If the project is not valid for rendering
|
|
"""
|
|
if project.is_empty():
|
|
raise ValueError("Project contains no modules to render")
|
|
|
|
def __repr__(self) -> str:
|
|
"""Return a string representation of the renderer.
|
|
|
|
Returns:
|
|
String representation showing the renderer name
|
|
"""
|
|
return f"{self.__class__.__name__}(name='{self.name}')" |