Files
doc-forge/docforge/renderers/base.py

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}')"