init docforge lib
This commit is contained in:
12
docforge/renderers/__init__.py
Normal file
12
docforge/renderers/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Renderers package for doc-forge output generation."""
|
||||
|
||||
from .base import DocRenderer, RendererConfig
|
||||
from .mkdocs import MkDocsRenderer
|
||||
from .sphinx import SphinxRenderer
|
||||
|
||||
__all__ = [
|
||||
"DocRenderer",
|
||||
"RendererConfig",
|
||||
"MkDocsRenderer",
|
||||
"SphinxRenderer",
|
||||
]
|
||||
51
docforge/renderers/__init__.pyi
Normal file
51
docforge/renderers/__init__.pyi
Normal file
@@ -0,0 +1,51 @@
|
||||
"""Type stubs for doc-forge renderers package."""
|
||||
|
||||
from typing import Any, Dict, List, Optional, Protocol, Union
|
||||
from pathlib import Path
|
||||
from docforge.model import Project
|
||||
|
||||
class DocRenderer(Protocol):
|
||||
"""Protocol for documentation renderers."""
|
||||
|
||||
name: str
|
||||
|
||||
def generate_sources(self, project: Project, out_dir: Path) -> None: ...
|
||||
|
||||
def build(self, config: 'RendererConfig') -> None: ...
|
||||
|
||||
def serve(self, config: 'RendererConfig') -> None: ...
|
||||
|
||||
class RendererConfig:
|
||||
"""Base configuration for renderers."""
|
||||
|
||||
out_dir: Path
|
||||
project: Project
|
||||
extra: Dict[str, Any]
|
||||
|
||||
def __init__(self, out_dir: Path, project: Project, extra: Optional[Dict[str, Any]] = None) -> None: ...
|
||||
|
||||
class MkDocsRenderer:
|
||||
"""MkDocs documentation renderer."""
|
||||
|
||||
name: str = "mkdocs"
|
||||
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
def generate_sources(self, project: Project, out_dir: Path) -> None: ...
|
||||
|
||||
def build(self, config: RendererConfig) -> None: ...
|
||||
|
||||
def serve(self, config: RendererConfig) -> None: ...
|
||||
|
||||
class SphinxRenderer:
|
||||
"""Sphinx documentation renderer."""
|
||||
|
||||
name: str = "sphinx"
|
||||
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
def generate_sources(self, project: Project, out_dir: Path) -> None: ...
|
||||
|
||||
def build(self, config: RendererConfig) -> None: ...
|
||||
|
||||
def serve(self, config: RendererConfig) -> None: ...
|
||||
217
docforge/renderers/base.py
Normal file
217
docforge/renderers/base.py
Normal file
@@ -0,0 +1,217 @@
|
||||
"""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}')"
|
||||
268
docforge/renderers/mkdocs.py
Normal file
268
docforge/renderers/mkdocs.py
Normal file
@@ -0,0 +1,268 @@
|
||||
"""MkDocs renderer for doc-forge.
|
||||
|
||||
The MkDocs renderer generates MkDocs-compatible documentation from the
|
||||
doc-forge documentation model. It creates Markdown files with mkdocstrings
|
||||
directives and generates the necessary MkDocs configuration.
|
||||
|
||||
This renderer follows the ADS specification by:
|
||||
- Emitting .md files with mkdocstrings directives
|
||||
- Using one file per module
|
||||
- Supporting build and serve operations via MkDocs APIs
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
try:
|
||||
import mkdocs
|
||||
import mkdocs.commands.build
|
||||
import mkdocs.commands.serve
|
||||
import mkdocs.config
|
||||
import yaml
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"mkdocs and mkdocstrings are required for MkDocs renderer. "
|
||||
"Install with: pip install doc-forge[mkdocs]"
|
||||
) from e
|
||||
|
||||
from docforge.model import Project, Module
|
||||
from .base import BaseRenderer, RendererConfig
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MkDocsRenderer(BaseRenderer):
|
||||
"""MkDocs documentation renderer.
|
||||
|
||||
The MkDocsRenderer converts the doc-forge documentation model into
|
||||
MkDocs-compatible source files. It generates Markdown files with
|
||||
mkdocstrings directives and creates the necessary MkDocs configuration.
|
||||
|
||||
Generated output structure:
|
||||
docs/
|
||||
├── index.md
|
||||
├── module1.md
|
||||
├── module2.md
|
||||
└── mkdocs.yml
|
||||
|
||||
Attributes:
|
||||
name: Renderer identifier ("mkdocs")
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the MkDocs renderer."""
|
||||
super().__init__("mkdocs")
|
||||
|
||||
def generate_sources(self, project: Project, out_dir: Path) -> None:
|
||||
"""Generate MkDocs source files.
|
||||
|
||||
Creates Markdown files for each module and generates the
|
||||
mkdocs.yml configuration file.
|
||||
|
||||
Args:
|
||||
project: The documentation project to render
|
||||
out_dir: Directory where source files should be written
|
||||
"""
|
||||
self.validate_project(project)
|
||||
self.ensure_output_dir(out_dir)
|
||||
|
||||
logger.info(f"Generating MkDocs sources in {out_dir}")
|
||||
|
||||
# Generate index.md
|
||||
self._generate_index(project, out_dir)
|
||||
|
||||
# Generate module files
|
||||
for module in project.get_all_modules():
|
||||
self._generate_module_file(module, out_dir)
|
||||
|
||||
# Generate mkdocs.yml
|
||||
self._generate_mkdocs_config(project, out_dir)
|
||||
|
||||
logger.info(f"Generated {len(project.get_all_modules())} module files")
|
||||
|
||||
def build(self, config: RendererConfig) -> None:
|
||||
"""Build MkDocs documentation.
|
||||
|
||||
Uses MkDocs build command to generate the final HTML documentation.
|
||||
|
||||
Args:
|
||||
config: Configuration for the build process
|
||||
"""
|
||||
self.validate_project(config.project)
|
||||
|
||||
mkdocs_yml = config.out_dir / "mkdocs.yml"
|
||||
if not mkdocs_yml.exists():
|
||||
raise ValueError(f"mkdocs.yml not found in {config.out_dir}")
|
||||
|
||||
logger.info(f"Building MkDocs documentation from {config.out_dir}")
|
||||
|
||||
# Load MkDocs configuration
|
||||
mkdocs_config = mkdocs.config.load_config(str(mkdocs_yml))
|
||||
|
||||
# Run build
|
||||
mkdocs.commands.build.build(mkdocs_config)
|
||||
|
||||
logger.info("MkDocs build completed successfully")
|
||||
|
||||
def serve(self, config: RendererConfig) -> None:
|
||||
"""Serve MkDocs documentation locally.
|
||||
|
||||
Starts the MkDocs development server for local documentation
|
||||
preview and testing.
|
||||
|
||||
Args:
|
||||
config: Configuration for the serve process
|
||||
"""
|
||||
self.validate_project(config.project)
|
||||
|
||||
mkdocs_yml = config.out_dir / "mkdocs.yml"
|
||||
if not mkdocs_yml.exists():
|
||||
raise ValueError(f"mkdocs.yml not found in {config.out_dir}")
|
||||
|
||||
# Get serve options from config
|
||||
host = config.get_extra("host", "127.0.0.1")
|
||||
port = config.get_extra("port", 8000)
|
||||
|
||||
logger.info(f"Serving MkDocs documentation at http://{host}:{port}")
|
||||
|
||||
# Load MkDocs configuration
|
||||
mkdocs_config = mkdocs.config.load_config(str(mkdocs_yml))
|
||||
|
||||
# Run serve
|
||||
mkdocs.commands.serve.serve(
|
||||
mkdocs_config,
|
||||
dev_addr=f"{host}:{port}",
|
||||
livereload="livereload" in config.extra,
|
||||
)
|
||||
|
||||
def _generate_index(self, project: Project, out_dir: Path) -> None:
|
||||
"""Generate the index.md file.
|
||||
|
||||
Args:
|
||||
project: The documentation project
|
||||
out_dir: Output directory
|
||||
"""
|
||||
index_path = out_dir / "index.md"
|
||||
|
||||
content = [f"# {project.name}"]
|
||||
|
||||
if project.version:
|
||||
content.append(f"\n**Version:** {project.version}")
|
||||
|
||||
content.append("\n## Modules")
|
||||
content.append("")
|
||||
|
||||
for entry in project.nav.entries:
|
||||
content.append(f"- [{entry.title}]({entry.path}.md)")
|
||||
|
||||
index_path.write_text("\n".join(content), encoding="utf-8")
|
||||
logger.debug(f"Generated {index_path}")
|
||||
|
||||
def _generate_module_file(self, module: Module, out_dir: Path) -> None:
|
||||
"""Generate a Markdown file for a module.
|
||||
|
||||
Args:
|
||||
module: The module to generate documentation for
|
||||
out_dir: Output directory
|
||||
"""
|
||||
module_path = out_dir / f"{module.path}.md"
|
||||
|
||||
content = [f"# {module.path}"]
|
||||
|
||||
if module.has_docstring():
|
||||
content.append(f"\n{module.docstring}")
|
||||
|
||||
content.append(f"\n::: {module.path}")
|
||||
content.append(" options:")
|
||||
content.append(" show_source: true")
|
||||
content.append(" show_root_heading: true")
|
||||
|
||||
module_path.write_text("\n".join(content), encoding="utf-8")
|
||||
logger.debug(f"Generated {module_path}")
|
||||
|
||||
def _generate_mkdocs_config(self, project: Project, out_dir: Path) -> None:
|
||||
"""Generate the mkdocs.yml configuration file.
|
||||
|
||||
Args:
|
||||
project: The documentation project
|
||||
out_dir: Output directory
|
||||
"""
|
||||
config_path = out_dir / "mkdocs.yml"
|
||||
|
||||
# Build navigation structure
|
||||
nav = []
|
||||
for entry in project.nav.entries:
|
||||
nav.append({entry.title: f"{entry.path}.md"})
|
||||
|
||||
# MkDocs configuration
|
||||
config = {
|
||||
"site_name": project.name,
|
||||
"site_description": f"Documentation for {project.name}",
|
||||
"nav": nav,
|
||||
"plugins": ["mkdocstrings"],
|
||||
"theme": {
|
||||
"name": "material",
|
||||
"features": ["navigation.instant", "navigation.tracking"],
|
||||
},
|
||||
"markdown_extensions": [
|
||||
"codehilite",
|
||||
"admonition",
|
||||
"toc",
|
||||
],
|
||||
"docs_dir": ".",
|
||||
"site_dir": "_site",
|
||||
}
|
||||
|
||||
if project.version:
|
||||
config["site_version"] = project.version
|
||||
|
||||
# Write configuration as YAML
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
||||
|
||||
logger.debug(f"Generated {config_path}")
|
||||
|
||||
|
||||
class MkDocsConfig(RendererConfig):
|
||||
"""MkDocs-specific renderer configuration.
|
||||
|
||||
Extends the base RendererConfig with MkDocs-specific options.
|
||||
|
||||
Attributes:
|
||||
theme: MkDocs theme to use
|
||||
extra_css: Additional CSS files
|
||||
extra_js: Additional JavaScript files
|
||||
plugins: MkDocs plugins to enable
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
out_dir: Path,
|
||||
project: Project,
|
||||
theme: str = "material",
|
||||
extra_css: Optional[List[str]] = None,
|
||||
extra_js: Optional[List[str]] = None,
|
||||
plugins: Optional[List[str]] = None,
|
||||
**extra,
|
||||
) -> None:
|
||||
"""Initialize MkDocs configuration.
|
||||
|
||||
Args:
|
||||
out_dir: Output directory for generated files
|
||||
project: The documentation project being rendered
|
||||
theme: MkDocs theme to use
|
||||
extra_css: Additional CSS files
|
||||
extra_js: Additional JavaScript files
|
||||
plugins: MkDocs plugins to enable
|
||||
**extra: Additional configuration options
|
||||
"""
|
||||
super().__init__(out_dir, project, extra)
|
||||
|
||||
self.theme = theme
|
||||
self.extra_css = extra_css or []
|
||||
self.extra_js = extra_js or []
|
||||
self.plugins = plugins or ["mkdocstrings"]
|
||||
317
docforge/renderers/sphinx.py
Normal file
317
docforge/renderers/sphinx.py
Normal file
@@ -0,0 +1,317 @@
|
||||
"""Sphinx renderer for doc-forge.
|
||||
|
||||
The Sphinx renderer generates Sphinx-compatible documentation from the
|
||||
doc-forge documentation model. It creates reStructuredText files with
|
||||
autodoc directives and generates the necessary Sphinx configuration.
|
||||
|
||||
This renderer follows the ADS specification by:
|
||||
- Emitting .rst files with autodoc directives
|
||||
- Supporting build operations via Sphinx APIs
|
||||
- Providing static build capability (serve is optional)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
try:
|
||||
import sphinx
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.util.docutils import docutils_namespace
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"sphinx is required for Sphinx renderer. "
|
||||
"Install with: pip install doc-forge[sphinx]"
|
||||
) from e
|
||||
|
||||
from docforge.model import Project, Module
|
||||
from .base import BaseRenderer, RendererConfig
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SphinxRenderer(BaseRenderer):
|
||||
"""Sphinx documentation renderer.
|
||||
|
||||
The SphinxRenderer converts the doc-forge documentation model into
|
||||
Sphinx-compatible source files. It generates reStructuredText files
|
||||
with autodoc directives and creates the necessary Sphinx configuration.
|
||||
|
||||
Generated output structure:
|
||||
docs/
|
||||
├── source/
|
||||
│ ├── index.rst
|
||||
│ ├── conf.py
|
||||
│ ├── module1.rst
|
||||
│ └── module2.rst
|
||||
└── build/
|
||||
|
||||
Attributes:
|
||||
name: Renderer identifier ("sphinx")
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the Sphinx renderer."""
|
||||
super().__init__("sphinx")
|
||||
|
||||
def generate_sources(self, project: Project, out_dir: Path) -> None:
|
||||
"""Generate Sphinx source files.
|
||||
|
||||
Creates reStructuredText files for each module and generates the
|
||||
Sphinx configuration (conf.py).
|
||||
|
||||
Args:
|
||||
project: The documentation project to render
|
||||
out_dir: Directory where source files should be written
|
||||
"""
|
||||
self.validate_project(project)
|
||||
self.ensure_output_dir(out_dir)
|
||||
|
||||
# Create source directory structure
|
||||
source_dir = out_dir / "source"
|
||||
source_dir.mkdir(exist_ok=True)
|
||||
|
||||
logger.info(f"Generating Sphinx sources in {source_dir}")
|
||||
|
||||
# Generate index.rst
|
||||
self._generate_index(project, source_dir)
|
||||
|
||||
# Generate conf.py
|
||||
self._generate_sphinx_config(project, source_dir)
|
||||
|
||||
# Generate module files
|
||||
for module in project.get_all_modules():
|
||||
self._generate_module_file(module, source_dir)
|
||||
|
||||
logger.info(f"Generated {len(project.get_all_modules())} module files")
|
||||
|
||||
def build(self, config: RendererConfig) -> None:
|
||||
"""Build Sphinx documentation.
|
||||
|
||||
Uses Sphinx application to generate the final HTML documentation.
|
||||
|
||||
Args:
|
||||
config: Configuration for the build process
|
||||
"""
|
||||
self.validate_project(config.project)
|
||||
|
||||
source_dir = config.out_dir / "source"
|
||||
build_dir = config.out_dir / "build"
|
||||
|
||||
if not source_dir.exists():
|
||||
raise ValueError(f"Source directory not found: {source_dir}")
|
||||
|
||||
logger.info(f"Building Sphinx documentation from {source_dir}")
|
||||
|
||||
# Get build options from config
|
||||
builder_name = config.get_extra("builder", "html")
|
||||
|
||||
# Create Sphinx application and build
|
||||
with docutils_namespace():
|
||||
app = Sphinx(
|
||||
srcdir=str(source_dir),
|
||||
confdir=str(source_dir),
|
||||
outdir=str(build_dir / builder_name),
|
||||
doctreedir=str(build_dir / "doctrees"),
|
||||
buildername=builder_name,
|
||||
verbosity=config.get_extra("verbosity", 1),
|
||||
)
|
||||
app.build()
|
||||
|
||||
logger.info(f"Sphinx build completed successfully ({builder_name})")
|
||||
|
||||
def serve(self, config: RendererConfig) -> None:
|
||||
"""Serve Sphinx documentation locally.
|
||||
|
||||
Sphinx doesn't have a built-in serve command like MkDocs. This
|
||||
method provides a simple static file server for the built HTML.
|
||||
|
||||
Args:
|
||||
config: Configuration for the serve process
|
||||
"""
|
||||
# Build first if needed
|
||||
build_dir = config.out_dir / "build" / "html"
|
||||
if not build_dir.exists():
|
||||
logger.info("Building documentation before serving...")
|
||||
self.build(config)
|
||||
|
||||
# Get serve options
|
||||
host = config.get_extra("host", "127.0.0.1")
|
||||
port = config.get_extra("port", 8000)
|
||||
|
||||
logger.info(f"Serving Sphinx documentation at http://{host}:{port}")
|
||||
logger.info(f"Serving from: {build_dir}")
|
||||
|
||||
# Simple HTTP server
|
||||
import http.server
|
||||
import socketserver
|
||||
import os
|
||||
|
||||
os.chdir(build_dir)
|
||||
|
||||
with socketserver.TCPServer((host, port), http.server.SimpleHTTPRequestHandler) as httpd:
|
||||
logger.info(f"Press Ctrl+C to stop serving")
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Stopping server...")
|
||||
httpd.shutdown()
|
||||
|
||||
def _generate_index(self, project: Project, source_dir: Path) -> None:
|
||||
"""Generate the index.rst file.
|
||||
|
||||
Args:
|
||||
project: The documentation project
|
||||
source_dir: Source directory
|
||||
"""
|
||||
index_path = source_dir / "index.rst"
|
||||
|
||||
content = [f"{project.name}", "=" * len(project.name), ""]
|
||||
|
||||
if project.version:
|
||||
content.append(f"**Version:** {project.version}")
|
||||
content.append("")
|
||||
|
||||
content.append(".. toctree::")
|
||||
content.append(" :maxdepth: 2")
|
||||
content.append(" :caption: Modules:")
|
||||
content.append("")
|
||||
|
||||
for entry in project.nav.entries:
|
||||
content.append(f" {entry.path}")
|
||||
|
||||
content.append("")
|
||||
content.append("Indices and tables")
|
||||
content.append("==================")
|
||||
content.append("")
|
||||
content.append("* :ref:`genindex`")
|
||||
content.append("* :ref:`modindex`")
|
||||
content.append("* :ref:`search`")
|
||||
|
||||
index_path.write_text("\n".join(content), encoding="utf-8")
|
||||
logger.debug(f"Generated {index_path}")
|
||||
|
||||
def _generate_module_file(self, module: Module, source_dir: Path) -> None:
|
||||
"""Generate a reStructuredText file for a module.
|
||||
|
||||
Args:
|
||||
module: The module to generate documentation for
|
||||
source_dir: Source directory
|
||||
"""
|
||||
module_path = source_dir / f"{module.path}.rst"
|
||||
|
||||
content = [f"{module.path}", "=" * len(module.path), ""]
|
||||
|
||||
if module.has_docstring():
|
||||
content.append(module.docstring)
|
||||
content.append("")
|
||||
|
||||
content.append(".. automodule:: " + module.path)
|
||||
content.append(" :members:")
|
||||
content.append(" :undoc-members:")
|
||||
content.append(" :show-inheritance:")
|
||||
|
||||
module_path.write_text("\n".join(content), encoding="utf-8")
|
||||
logger.debug(f"Generated {module_path}")
|
||||
|
||||
def _generate_sphinx_config(self, project: Project, source_dir: Path) -> None:
|
||||
"""Generate the conf.py configuration file.
|
||||
|
||||
Args:
|
||||
project: The documentation project
|
||||
source_dir: Source directory
|
||||
"""
|
||||
config_path = source_dir / "conf.py"
|
||||
|
||||
content = [
|
||||
f'"""Sphinx configuration for {project.name}."""',
|
||||
"",
|
||||
"import os",
|
||||
"import sys",
|
||||
"",
|
||||
"# Add the project root to the Python path",
|
||||
"sys.path.insert(0, os.path.abspath('../..'))",
|
||||
"",
|
||||
f"# Project information",
|
||||
f"project = '{project.name}'",
|
||||
"copyright = '2024, Project Authors'",
|
||||
"author = 'Project Authors'",
|
||||
f"release = '{project.version or '0.1.0'}'",
|
||||
"",
|
||||
"# General configuration",
|
||||
"extensions = [",
|
||||
" 'sphinx.ext.autodoc',",
|
||||
" 'sphinx.ext.viewcode',",
|
||||
" 'sphinx.ext.napoleon',",
|
||||
" 'sphinx.ext.intersphinx',",
|
||||
"]",
|
||||
"",
|
||||
"# Templates path",
|
||||
"templates_path = ['_templates']",
|
||||
"",
|
||||
"# Output formatting",
|
||||
"html_theme = 'alabaster'",
|
||||
"html_static_path = ['_static']",
|
||||
"",
|
||||
"# Autodoc settings",
|
||||
"autodoc_default_options = {",
|
||||
" 'members': True,",
|
||||
" 'member-order': 'bysource',",
|
||||
" 'special-members': '__init__',",
|
||||
" 'undoc-members': True,",
|
||||
" 'exclude-members': '__weakref__'",
|
||||
"}",
|
||||
"",
|
||||
"# Napoleon settings",
|
||||
"napoleon_google_docstring = True",
|
||||
"napoleon_numpy_docstring = True",
|
||||
"napoleon_include_init_with_doc = False",
|
||||
"napoleon_include_private_with_doc = False",
|
||||
]
|
||||
|
||||
config_path.write_text("\n".join(content), encoding="utf-8")
|
||||
logger.debug(f"Generated {config_path}")
|
||||
|
||||
|
||||
class SphinxConfig(RendererConfig):
|
||||
"""Sphinx-specific renderer configuration.
|
||||
|
||||
Extends the base RendererConfig with Sphinx-specific options.
|
||||
|
||||
Attributes:
|
||||
builder: Sphinx builder to use (html, latex, etc.)
|
||||
theme: HTML theme to use
|
||||
extensions: Sphinx extensions to enable
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
out_dir: Path,
|
||||
project: Project,
|
||||
builder: str = "html",
|
||||
theme: str = "alabaster",
|
||||
extensions: Optional[List[str]] = None,
|
||||
**extra,
|
||||
) -> None:
|
||||
"""Initialize Sphinx configuration.
|
||||
|
||||
Args:
|
||||
out_dir: Output directory for generated files
|
||||
project: The documentation project being rendered
|
||||
builder: Sphinx builder to use
|
||||
theme: HTML theme to use
|
||||
extensions: Sphinx extensions to enable
|
||||
**extra: Additional configuration options
|
||||
"""
|
||||
super().__init__(out_dir, project, extra)
|
||||
|
||||
self.builder = builder
|
||||
self.theme = theme
|
||||
self.extensions = extensions or [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.napoleon",
|
||||
]
|
||||
Reference in New Issue
Block a user