init docforge lib
This commit is contained in:
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