Improve documentation look & feel via MkDocs Material template enhancements (#5)

# Improve documentation look & feel via MkDocs Material template enhancements

## Summary

This MR improves the overall **documentation experience and visual presentation** of the doc-forge docs by enhancing the MkDocs Material template configuration.

The changes focus on **navigation usability, code readability, and richer Markdown rendering**, resulting in a cleaner and more professional documentation site.

Docstring changes were made across the codebase for consistency, but this MR description focuses on the **template and presentation improvements**.

---

## Navigation Improvements

The navigation system has been enhanced to provide a clearer structure and better discoverability.

Key improvements include:

* Section-aware navigation in the sidebar
* Automatic expansion of module/package hierarchy
* Scroll tracking within the sidebar
* Clickable package index pages

Material navigation features added:

* `navigation.sections`
* `navigation.expand`
* `navigation.tracking`
* `navigation.indexes`

This results in a **single cohesive navigation tree** that exposes the entire documentation hierarchy from the sidebar.

---

## Code Block Improvements

Code blocks previously appeared relatively plain. The template now enables richer syntax highlighting and improved readability.

Enhancements include:

* Syntax highlighting using `pymdownx.highlight`
* Line numbers for code blocks
* Anchored line numbers for deep linking
* Improved fenced code block rendering

Additional Material features:

* `content.code.copy` — copy button for code blocks
* `content.code.annotate` — support for code annotations

These changes significantly improve the readability of examples and API snippets throughout the documentation.

---

## Markdown Rendering Enhancements

Additional Markdown extensions were enabled to support richer documentation features:

* `pymdownx.superfences` for advanced fenced blocks
* `pymdownx.inlinehilite` for inline code highlighting
* `pymdownx.snippets` for reusable snippets
* `admonition` and `pymdownx.details` for callouts and collapsible sections
* `pymdownx.tabbed` for tabbed content blocks
* `pymdownx.tasklist` for checklist-style items
* `tables`, `footnotes`, and advanced formatting extensions

These extensions make it easier to write expressive and structured documentation.

---

## Search Experience

The documentation search experience has been improved using Material search features:

* `search.highlight`
* `search.share`
* `search.suggest`

These enhancements provide:

* highlighted search matches
* sharable search URLs
* auto-suggestions while typing

---

## mkdocstrings Improvements

The mkdocstrings configuration has been expanded to produce clearer API documentation.

Notable improvements include:

* grouping objects by category
* explicit category headings
* improved symbol headings
* cleaner object path display

This results in more structured API documentation pages.

---

## Result

Overall, these changes provide:

* cleaner and more intuitive navigation
* significantly improved code presentation
* richer Markdown capabilities
* better search usability

The documentation now has a **more polished, modern appearance** and improved usability for both readers and contributors.

Reviewed-on: #5
Co-authored-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
Co-committed-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
This commit is contained in:
2026-03-07 10:50:18 +00:00
committed by aetos
parent f8ca6075fb
commit b6306baafc
51 changed files with 2222 additions and 1239 deletions

View File

@@ -1,20 +1,34 @@
"""
# Renderers Layer
Renderers layer for doc-forge.
The `docforge.renderers` package handles the transformation of the internal
documentation models into physical files formatted for specific documentation
engines.
The ``docforge.renderers`` package transforms the internal documentation
models into files formatted for specific documentation systems.
## Current Implementations
---
- **MkDocsRenderer**: Generates Markdown files utilizing the `mkdocstrings`
syntax. It automatically handles package/module hierarchy and generates
`index.md` files for packages.
Overview
--------
## Extending
Renderers consume the doc-forge project model and generate output suitable
for documentation tools or machine interfaces.
To add a new renderer, implement the `DocRenderer` protocol defined in
`docforge.renderers.base`.
Current implementations:
- **MkDocsRenderer** Produces Markdown files compatible with MkDocs and
the ``mkdocstrings`` plugin. It automatically handles package hierarchy
and generates ``index.md`` files for packages.
- **MCPRenderer** Emits structured JSON resources designed for consumption
by Model Context Protocol (MCP) clients.
---
Extending
---------
New renderers can be added by implementing the ``DocRenderer`` protocol
defined in ``docforge.renderers.base``.
---
"""
from .mkdocs_renderer import MkDocsRenderer

View File

@@ -1,7 +1,9 @@
"""
This module defines the base interfaces and configuration containers for
doc-forge renderers. All renderer implementations should adhere to the
DocRenderer protocol.
Renderer base interfaces and configuration models.
This module defines the base protocol and configuration container used by
doc-forge renderers. Concrete renderer implementations should implement the
``DocRenderer`` protocol.
"""
from pathlib import Path
@@ -14,12 +16,22 @@ class RendererConfig:
"""
Configuration container for documentation renderers.
Args:
out_dir: The directory where documentation files should be written.
project: The introspected project models to be rendered.
A ``RendererConfig`` instance groups together the project model and the
output directory used during rendering.
Attributes:
out_dir: Directory where generated documentation files will be written.
project: Documentation project model to be rendered.
"""
def __init__(self, out_dir: Path, project: Project) -> None:
"""
Initialize a RendererConfig instance.
Args:
out_dir: Target directory where documentation files should be written.
project: Introspected project model to render.
"""
self.out_dir = out_dir
self.project = project
@@ -27,6 +39,9 @@ class RendererConfig:
class DocRenderer(Protocol):
"""
Protocol defining the interface for documentation renderers.
Implementations of this protocol are responsible for transforming a
``Project`` model into renderer-specific documentation sources.
"""
name: str
@@ -37,10 +52,11 @@ class DocRenderer(Protocol):
out_dir: Path,
) -> None:
"""
Generate renderer-specific source files for the given project.
Generate renderer-specific documentation sources.
Args:
project: The project models containing modules and objects.
out_dir: Target directory for the generated files.
project: Project model containing modules and documentation objects.
out_dir: Directory where generated documentation sources
should be written.
"""
...

View File

@@ -7,18 +7,25 @@ from docforge.models import Project, Module, DocObject
class MCPRenderer:
"""
Renderer that emits MCP-native JSON resources from docforge models.
Renderer that generates MCP-compatible documentation resources.
This renderer converts doc-forge project models into structured JSON
resources suitable for consumption by systems implementing the Model
Context Protocol (MCP).
"""
name = "mcp"
def generate_sources(self, project: Project, out_dir: Path) -> None:
"""
Generate MCP-compatible JSON resources and navigation for the project.
Generate MCP documentation resources for a project.
The renderer serializes each module into a JSON resource and produces
supporting metadata files such as ``nav.json`` and ``index.json``.
Args:
project: The project model to render.
out_dir: Target directory for the generated JSON files.
project: Documentation project model to render.
out_dir: Directory where MCP resources will be written.
"""
modules_dir = out_dir / "modules"
modules_dir.mkdir(parents=True, exist_ok=True)
@@ -54,11 +61,11 @@ class MCPRenderer:
def _write_module(self, module: Module, modules_dir: Path) -> None:
"""
Serialize a module into an MCP JSON resource on disk.
Serialize a module into an MCP resource file.
Args:
module: The module instance to serialize.
modules_dir: The directory where the module JSON file should be written.
module: Module instance to serialize.
modules_dir: Directory where module JSON files are stored.
"""
payload = {
"module": module.path,
@@ -71,13 +78,13 @@ class MCPRenderer:
def _render_module(self, module: Module) -> Dict:
"""
Render a Module into MCP-friendly structured data.
Convert a Module model into MCP-compatible structured data.
Args:
module: The module instance to render.
module: Module instance to convert.
Returns:
A dictionary following the MCP documentation resource schema.
Dictionary representing the module and its documented objects.
"""
data: Dict = {
"path": module.path,
@@ -92,13 +99,13 @@ class MCPRenderer:
def _render_object(self, obj: DocObject) -> Dict:
"""
Recursively render a DocObject into structured MCP data.
Recursively convert a DocObject into structured MCP data.
Args:
obj: The documented object (class, func, etc.) to render.
obj: Documentation object to convert.
Returns:
A dictionary representing the object and its members.
Dictionary describing the object and any nested members.
"""
data: Dict = {
"name": obj.name,
@@ -119,4 +126,13 @@ class MCPRenderer:
@staticmethod
def _json(data: Dict) -> str:
"""
Serialize data to formatted JSON.
Args:
data: Dictionary to serialize.
Returns:
JSON string formatted with indentation and UTF-8 compatibility.
"""
return json.dumps(data, indent=2, ensure_ascii=False)

View File

@@ -1,12 +1,16 @@
"""
MkDocsRenderer
MkDocs renderer implementation.
Generates Markdown source files compatible with MkDocs Material
and mkdocstrings, ensuring:
This module defines the ``MkDocsRenderer`` class, which generates Markdown
documentation sources compatible with MkDocs Material and the mkdocstrings
plugin.
- Root index.md always exists
- Parent package indexes are created automatically
- Child modules are linked in parent index files
The renderer ensures a consistent documentation structure by:
- Creating a root ``index.md`` if one does not exist
- Generating package index pages automatically
- Linking child modules within parent package pages
- Optionally generating ``README.md`` from the root package docstring
"""
from pathlib import Path
@@ -15,8 +19,10 @@ from docforge.models import Project, Module
class MkDocsRenderer:
"""
Renderer that generates Markdown source files formatted for the MkDocs
'mkdocstrings' plugin.
Renderer that produces Markdown documentation for MkDocs.
Generated pages use mkdocstrings directives to reference Python modules,
allowing MkDocs to render API documentation dynamically.
"""
name = "mkdocs"
@@ -24,6 +30,7 @@ class MkDocsRenderer:
# -------------------------
# Public API
# -------------------------
def generate_sources(
self,
project: Project,
@@ -31,13 +38,17 @@ class MkDocsRenderer:
module_is_source: bool | None = None,
) -> None:
"""
Produce a set of Markdown files in the output directory based on the
provided Project models.
Generate Markdown documentation files for a project.
This method renders a documentation structure from the provided
project model and writes the resulting Markdown files to the
specified output directory.
Args:
project: The project models to render.
out_dir: Target directory for documentation files.
module_is_source: Module is the source folder and to be treated as the root folder.
project: Project model containing modules to document.
out_dir: Directory where generated Markdown files will be written.
module_is_source: If True, treat the specified module as the
documentation root rather than nesting it inside a folder.
"""
out_dir.mkdir(parents=True, exist_ok=True)
self._ensure_root_index(project, out_dir)
@@ -45,7 +56,7 @@ class MkDocsRenderer:
modules = list(project.get_all_modules())
paths = {m.path for m in modules}
# Package detection (level-agnostic)
# Detect packages (modules with children)
packages = {
p for p in paths
if any(other.startswith(p + ".") for other in paths)
@@ -59,9 +70,82 @@ class MkDocsRenderer:
module_is_source,
)
def generate_readme(
self,
project: Project,
docs_dir: Path,
module_is_source: bool | None = None,
) -> None:
"""
Generate a ``README.md`` file from the root module docstring.
Behavior:
- If ``module_is_source`` is True, ``README.md`` is written to the
project root directory.
- If False, README generation is currently not implemented.
Args:
project: Project model containing documentation metadata.
docs_dir: Directory containing generated documentation sources.
module_is_source: Whether the module is treated as the project
source root.
"""
if not module_is_source:
# Future: support README generation per module
return
readme_path = docs_dir.parent / "README.md"
root_module = None
for module in project.get_all_modules():
if module.path == project.name:
root_module = module
break
if root_module is None:
return
doc = ""
if root_module.docstring:
doc = getattr(
root_module.docstring,
"value",
str(root_module.docstring),
)
content = (
f"# {project.name}\n\n"
f"{doc.strip()}\n"
)
if not readme_path.exists() or readme_path.read_text(encoding="utf-8") != content:
readme_path.write_text(
content,
encoding="utf-8",
)
# -------------------------
# Internal helpers
# -------------------------
def _find_root_module(self, project: Project) -> Module | None:
"""
Locate the root module corresponding to the project name.
Args:
project: Project model to inspect.
Returns:
The root ``Module`` if found, otherwise ``None``.
"""
for module in project.get_all_modules():
if module.path == project.name:
return module
return None
def _write_module(
self,
module: Module,
@@ -70,15 +154,20 @@ class MkDocsRenderer:
module_is_source: bool | None = None,
) -> None:
"""
Write a single module's documentation file. Packages are written as
'index.md' inside their respective directories.
Write documentation for a single module.
Package modules are rendered as ``index.md`` files inside their
corresponding directories, while leaf modules are written as
standalone Markdown pages.
Args:
module: The module to write.
packages: A set of module paths that are identified as packages.
out_dir: The base output directory.
module_is_source: Module is the source folder and to be treated as the root folder.
module: Module to render.
packages: Set of module paths identified as packages.
out_dir: Base directory for generated documentation files.
module_is_source: Whether the module acts as the documentation
root directory.
"""
parts = module.path.split(".")
if module_is_source:
@@ -87,15 +176,15 @@ class MkDocsRenderer:
module_name, parts = parts[0], parts
if module.path in packages:
# Package → directory/index.md
dir_path = out_dir.joinpath(*parts)
dir_path.mkdir(parents=True, exist_ok=True)
md_path = dir_path / "index.md"
link_target = f"{parts[-1]}/" if parts else None
else:
# Leaf module → parent_dir/<name>.md
dir_path = out_dir.joinpath(*parts[:-1])
dir_path.mkdir(parents=True, exist_ok=True)
md_path = dir_path / f"{parts[-1]}.md"
link_target = f"{parts[-1]}.md" if parts else None
@@ -110,14 +199,14 @@ class MkDocsRenderer:
def _render_markdown(self, title: str, module_path: str) -> str:
"""
Generate the Markdown content for a module file.
Generate Markdown content for a module documentation page.
Args:
title: The display title for the page.
module_path: The dotted path of the module to document.
title: Page title displayed in the documentation.
module_path: Dotted import path of the module.
Returns:
A string containing the Markdown source.
Markdown source containing a mkdocstrings directive.
"""
return (
f"# {title}\n\n"
@@ -127,8 +216,15 @@ class MkDocsRenderer:
def _ensure_root_index(
self,
project: Project,
out_dir: Path
out_dir: Path,
) -> None:
"""
Ensure that the root ``index.md`` page exists.
Args:
project: Project model used for the page title.
out_dir: Documentation output directory.
"""
root_index = out_dir / "index.md"
if not root_index.exists():
@@ -145,16 +241,23 @@ class MkDocsRenderer:
link_target: str,
title: str,
) -> None:
"""
Ensure that parent package index files exist and contain links to
child modules.
Args:
parts: Module path components.
out_dir: Documentation output directory.
link_target: Link target used in the parent index.
title: Display title for the link.
"""
if len(parts) == 1:
parent_index = out_dir / "index.md"
link = f"- [{title}]({link_target})\n"
else:
parent_dir = out_dir.joinpath(*parts[:-1])
parent_dir.mkdir(parents=True, exist_ok=True)
parent_index = parent_dir / "index.md"
link = f"- [{title}]({link_target})\n"
if not parent_index.exists():
parent_title = parts[-2].replace("_", " ").title()
parent_index.write_text(
@@ -164,5 +267,6 @@ class MkDocsRenderer:
content = parent_index.read_text(encoding="utf-8")
link = f"- [{title}]({link_target})\n"
if link not in content:
parent_index.write_text(content + link, encoding="utf-8")

View File

@@ -12,6 +12,13 @@ class MkDocsRenderer:
module_is_source: bool | None = None,
) -> None: ...
def generate_readme(
self,
project: Project,
docs_dir: Path,
module_is_source: bool | None = None,
) -> None:
def _write_module(
self,
module: Module,