updated doc strings

This commit is contained in:
2026-03-07 15:44:02 +05:30
parent 73b15cc3ab
commit 17d39a3e88
21 changed files with 680 additions and 330 deletions

View File

@@ -1,14 +1,27 @@
""" """
# CLI Layer Command line interface entry point for doc-forge.
The `docforge.cli` package provides the command-line interface for interacting This module exposes the primary CLI entry function used by the
with doc-forge. ``doc-forge`` command. The actual command implementation resides in
``docforge.cli.main``, while this module provides a stable import path
for external tools and the package entry point configuration.
## Available Commands The CLI is responsible for orchestrating documentation workflows such as
generating renderer sources, building documentation sites, exporting
machine-readable documentation bundles, and starting development or MCP
servers.
- **build**: Build documentation (MkDocs site or MCP resources). Typical usage
- **serve**: Serve documentation (MkDocs or MCP). -------------
- **tree**: Visualize the introspected project structure.
The CLI is normally invoked through the installed command:
doc-forge <command> [options]
Programmatic invocation is also possible:
from docforge.cli import main
main()
""" """
from .main import main from .main import main

View File

@@ -9,8 +9,10 @@ from docforge.cli import mcp_utils
@click.group() @click.group()
def cli() -> None: def cli() -> None:
""" """
doc-forge CLI: A tool for introspecting Python projects and generating Root command group for the doc-forge CLI.
documentation.
Provides commands for building, serving, and inspecting
documentation generated from Python source code.
""" """
pass pass
@@ -21,14 +23,12 @@ def cli() -> None:
@click.option("--module-is-source", is_flag=True, help="Module is source folder and to be treated as root folder") @click.option("--module-is-source", is_flag=True, help="Module is source folder and to be treated as root folder")
@click.option("--module", help="Python module to document") @click.option("--module", help="Python module to document")
@click.option("--project-name", help="Project name override") @click.option("--project-name", help="Project name override")
# MkDocs specific
@click.option("--site-name", help="MkDocs site name") @click.option("--site-name", help="MkDocs site name")
@click.option("--docs-dir", type=click.Path(path_type=Path), default=Path("docs"), help="Directory for MD sources") @click.option("--docs-dir", type=click.Path(path_type=Path), default=Path("docs"), help="Directory for MD sources")
@click.option("--nav", "nav_file", type=click.Path(path_type=Path), default=Path("docforge.nav.yml"), @click.option("--nav", "nav_file", type=click.Path(path_type=Path), default=Path("docforge.nav.yml"),
help="Nav spec path") help="Nav spec path")
@click.option("--template", type=click.Path(path_type=Path), help="MkDocs template path") @click.option("--template", type=click.Path(path_type=Path), help="MkDocs template path")
@click.option("--mkdocs-yml", type=click.Path(path_type=Path), default=Path("mkdocs.yml"), help="Output config path") @click.option("--mkdocs-yml", type=click.Path(path_type=Path), default=Path("mkdocs.yml"), help="Output config path")
# MCP specific
@click.option("--out-dir", type=click.Path(path_type=Path), default=Path("mcp_docs"), help="MCP output directory") @click.option("--out-dir", type=click.Path(path_type=Path), default=Path("mcp_docs"), help="MCP output directory")
def build( def build(
mcp: bool, mcp: bool,
@@ -44,25 +44,36 @@ def build(
out_dir: Path, out_dir: Path,
) -> None: ) -> None:
""" """
Build documentation (MkDocs site or MCP resources). Build documentation artifacts.
This command orchestrates the full build process: This command performs the full documentation build pipeline:
1. Introspects the code (Griffe)
2. Renders sources (MkDocs Markdown or MCP JSON) 1. Introspects the Python project using Griffe
3. (MkDocs only) Generates config and runs the final site build. 2. Generates renderer-specific documentation sources
3. Optionally builds the final documentation output
Depending on the selected options, the build can target:
- MkDocs static documentation sites
- MCP structured documentation resources
Args: Args:
mcp: Use the MCP documentation builder. mcp: Enable MCP documentation generation.
mkdocs: Use the MkDocs documentation builder. mkdocs: Enable MkDocs documentation generation.
module_is_source: Module is the source folder and to be treated as the root folder. module_is_source: Treat the specified module directory as the
module: The dotted path of the module to the document. project root.
module: Python module import path to document.
project_name: Optional override for the project name. project_name: Optional override for the project name.
site_name: (MkDocs) The site display name. Defaults to module name. site_name: Display name for the MkDocs site.
docs_dir: (MkDocs) Target directory for Markdown sources. docs_dir: Directory where Markdown documentation sources
nav_file: (MkDocs) Path to the docforge.nav.yml specification. will be generated.
template: (MkDocs) Optional custom mkdocs.yml template. nav_file: Path to the navigation specification file.
mkdocs_yml: (MkDocs) Target path for the generated mkdocs.yml. template: Optional custom MkDocs configuration template.
out_dir: (MCP) Target directory for MCP JSON resources. mkdocs_yml: Output path for the generated MkDocs configuration.
out_dir: Output directory for generated MCP resources.
Raises:
click.UsageError: If required options are missing or conflicting.
""" """
if not mcp and not mkdocs: if not mcp and not mkdocs:
raise click.UsageError("Must specify either --mcp or --mkdocs") raise click.UsageError("Must specify either --mcp or --mkdocs")
@@ -111,14 +122,22 @@ def serve(
out_dir: Path, out_dir: Path,
) -> None: ) -> None:
""" """
Serve documentation (MkDocs or MCP). Serve generated documentation locally.
Depending on the selected mode, this command starts either:
- A MkDocs development server for browsing documentation
- An MCP server exposing structured documentation resources
Args: Args:
mcp: Serve MCP resources via an MCP server. mcp: Serve documentation using the MCP server.
mkdocs: Serve the MkDocs site using the built-in development server. mkdocs: Serve the MkDocs development site.
module: The dotted path of the module to serve. module: Python module import path to serve via MCP.
mkdocs_yml: (MkDocs) Path to the mkdocs.yml configuration. mkdocs_yml: Path to the MkDocs configuration file.
out_dir: (MCP) Path to the mcp_docs/ directory. out_dir: Root directory containing MCP documentation resources.
Raises:
click.UsageError: If invalid or conflicting options are provided.
""" """
if mcp and mkdocs: if mcp and mkdocs:
raise click.UsageError("Cannot specify both --mcp and --mkdocs") raise click.UsageError("Cannot specify both --mcp and --mkdocs")
@@ -148,11 +167,15 @@ def tree(
project_name: Optional[str], project_name: Optional[str],
) -> None: ) -> None:
""" """
Visualize the project structure in the terminal. Display the documentation object tree for a module.
This command introspects the specified module and prints a
hierarchical representation of the discovered documentation
objects, including modules, classes, functions, and members.
Args: Args:
module: The module import path to recursively introspect. module: Python module import path to introspect.
project_name: Optional override for the project name shown at the root. project_name: Optional name to display as the project root.
""" """
loader = GriffeLoader() loader = GriffeLoader()
project = loader.load_project([module], project_name) project = loader.load_project([module], project_name)
@@ -167,11 +190,14 @@ def tree(
def _print_object(obj, indent: str) -> None: def _print_object(obj, indent: str) -> None:
""" """
Recursive helper to print doc objects and their members to the console. Recursively print a documentation object and its members.
This helper function traverses the documentation object graph
and prints each object with indentation to represent hierarchy.
Args: Args:
obj: The DocObject instance to print. obj: Documentation object to print.
indent: Current line indentation (e.g., ''). indent: Current indentation prefix used for nested members.
""" """
click.echo(f"{indent}├── {obj.name}") click.echo(f"{indent}├── {obj.name}")
for member in obj.get_all_members(): for member in obj.get_all_members():

View File

@@ -1,14 +1,23 @@
""" """
Main entry point for the doc-forge CLI. This module delegates all command Command-line entry point for the doc-forge CLI.
execution to docforge.cli.commands.
This module exposes the executable entry point that initializes the
Click command group defined in ``docforge.cli.commands``.
""" """
from docforge.cli.commands import cli from docforge.cli.commands import cli
def main() -> None: def main() -> None:
""" """
CLI Entry point. Boots the click application. Run the doc-forge command-line interface.
This function initializes and executes the Click CLI application.
It is used as the console entry point when invoking ``doc-forge``
from the command line.
""" """
cli() cli()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -4,14 +4,22 @@ from docforge.loaders import GriffeLoader, discover_module_paths
from docforge.renderers import MCPRenderer from docforge.renderers import MCPRenderer
from docforge.servers import MCPServer from docforge.servers import MCPServer
def generate_resources(module: str, project_name: str | None, out_dir: Path) -> None: def generate_resources(module: str, project_name: str | None, out_dir: Path) -> None:
""" """
Generate MCP-compatible documentation resources. Generate MCP documentation resources from a Python module.
The function performs project introspection, builds the internal
documentation model, and renders MCP-compatible JSON resources
to the specified output directory.
Args: Args:
module: The dotted path of the primary module to document. module: Python module import path used as the entry point for
project_name: Optional override for the project name. documentation generation.
out_dir: Directory where the MCP JSON resources and nav will be written. project_name: Optional override for the project name used in
generated documentation metadata.
out_dir: Directory where MCP resources (index.json, nav.json,
and module data) will be written.
""" """
loader = GriffeLoader() loader = GriffeLoader()
discovered_paths = discover_module_paths(module) discovered_paths = discover_module_paths(module)
@@ -20,13 +28,23 @@ def generate_resources(module: str, project_name: str | None, out_dir: Path) ->
renderer = MCPRenderer() renderer = MCPRenderer()
renderer.generate_sources(project, out_dir) renderer.generate_sources(project, out_dir)
def serve(module: str, mcp_root: Path) -> None: def serve(module: str, mcp_root: Path) -> None:
""" """
Serve MCP documentation from a pre-built bundle. Start an MCP server for a pre-generated documentation bundle.
The server exposes documentation resources such as project metadata,
navigation structure, and module documentation through MCP endpoints.
Args: Args:
module: The dotted path of the primary module to serve. module: Python module import path used to identify the served
mcp_root: Path to the directory containing index.json, nav.json, and modules/. documentation instance.
mcp_root: Path to the directory containing the MCP documentation
bundle (index.json, nav.json, and modules/).
Raises:
click.ClickException: If the MCP documentation bundle is missing
required files or directories.
""" """
if not mcp_root.exists(): if not mcp_root.exists():
raise click.ClickException(f"mcp_docs directory not found: {mcp_root}") raise click.ClickException(f"mcp_docs directory not found: {mcp_root}")

View File

@@ -6,6 +6,7 @@ from docforge.loaders import GriffeLoader, discover_module_paths
from docforge.renderers import MkDocsRenderer from docforge.renderers import MkDocsRenderer
from docforge.nav import load_nav_spec, resolve_nav, MkDocsNavEmitter from docforge.nav import load_nav_spec, resolve_nav, MkDocsNavEmitter
def generate_sources( def generate_sources(
module: str, module: str,
docs_dir: Path, docs_dir: Path,
@@ -13,13 +14,20 @@ def generate_sources(
module_is_source: bool | None = None, module_is_source: bool | None = None,
) -> None: ) -> None:
""" """
Generate Markdown source files for the specified module. Generate MkDocs Markdown sources for a Python module.
This function introspects the specified module, builds the internal
documentation model, and renders Markdown documentation files for
use with MkDocs.
Args: Args:
module: The dotted path of the primary module to document. module: Python module import path used as the entry point for
project_name: Optional override for the project name. documentation generation.
docs_dir: Directory where the generated Markdown files will be written. docs_dir: Directory where the generated Markdown files will be written.
module_is_source: Module is the source folder and to be treated as the root folder. project_name: Optional override for the project name used in
documentation metadata.
module_is_source: If True, treat the specified module directory as
the project root rather than a nested module.
""" """
loader = GriffeLoader() loader = GriffeLoader()
discovered_paths = discover_module_paths(module) discovered_paths = discover_module_paths(module)
@@ -38,16 +46,33 @@ def generate_sources(
module_is_source, module_is_source,
) )
def generate_config(docs_dir: Path, nav_file: Path, template: Path | None, out: Path, site_name: str) -> None:
def generate_config(
docs_dir: Path,
nav_file: Path,
template: Path | None,
out: Path,
site_name: str,
) -> None:
""" """
Generate an mkdocs.yml configuration file. Generate an ``mkdocs.yml`` configuration file.
The configuration is created by combining a template configuration
with a navigation structure derived from the docforge navigation
specification.
Args: Args:
docs_dir: Path to the directory containing documentation Markdown files. docs_dir: Directory containing generated documentation Markdown files.
nav_file: Path to the docforge.nav.yml specification. nav_file: Path to the ``docforge.nav.yml`` navigation specification.
template: Optional path to an mkdocs.yml template (overrides built-in). template: Optional path to a custom MkDocs configuration template.
out: Path where the final mkdocs.yml will be written. If not provided, a built-in template will be used.
site_name: The display name for the documentation site. out: Destination path where the generated ``mkdocs.yml`` file
will be written.
site_name: Display name for the generated documentation site.
Raises:
click.FileError: If the navigation specification or template
file cannot be found.
""" """
if not nav_file.exists(): if not nav_file.exists():
raise click.FileError(str(nav_file), hint="Nav spec not found") raise click.FileError(str(nav_file), hint="Nav spec not found")
@@ -74,12 +99,19 @@ def generate_config(docs_dir: Path, nav_file: Path, template: Path | None, out:
out.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8") out.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
def build(mkdocs_yml: Path) -> None: def build(mkdocs_yml: Path) -> None:
""" """
Build the documentation site using MkDocs. Build the MkDocs documentation site.
This function loads the MkDocs configuration and runs the MkDocs
build command to generate the final static documentation site.
Args: Args:
mkdocs_yml: Path to the mkdocs.yml configuration file. mkdocs_yml: Path to the ``mkdocs.yml`` configuration file.
Raises:
click.ClickException: If the configuration file does not exist.
""" """
if not mkdocs_yml.exists(): if not mkdocs_yml.exists():
raise click.ClickException(f"mkdocs.yml not found: {mkdocs_yml}") raise click.ClickException(f"mkdocs.yml not found: {mkdocs_yml}")
@@ -89,12 +121,19 @@ def build(mkdocs_yml: Path) -> None:
mkdocs_build(load_config(str(mkdocs_yml))) mkdocs_build(load_config(str(mkdocs_yml)))
def serve(mkdocs_yml: Path) -> None: def serve(mkdocs_yml: Path) -> None:
""" """
Serve the documentation site with live-reload using MkDocs. Start an MkDocs development server with live reload.
The server watches documentation files and automatically reloads
the site when changes are detected.
Args: Args:
mkdocs_yml: Path to the mkdocs.yml configuration file. mkdocs_yml: Path to the ``mkdocs.yml`` configuration file.
Raises:
click.ClickException: If the configuration file does not exist.
""" """
if not mkdocs_yml.exists(): if not mkdocs_yml.exists():
raise click.ClickException(f"mkdocs.yml not found: {mkdocs_yml}") raise click.ClickException(f"mkdocs.yml not found: {mkdocs_yml}")

View File

@@ -1,17 +1,23 @@
""" """
# Loader Layer Loader layer for doc-forge.
The `docforge.loaders` package is responsible for discovering Python source files The ``docforge.loaders`` package is responsible for discovering Python modules
and extracting their documentation using static analysis. and extracting documentation data using static analysis.
## Core Features Overview
--------
- **Discovery**: Automatically find all modules and packages in a project This layer converts Python source code into an intermediate documentation
directory. model used by doc-forge. It performs module discovery, introspection, and
- **Introspection**: Uses `griffe` to parse docstrings, signatures, and initial filtering before the data is passed to the core documentation models.
hierarchical relationships without executing the code.
- **Filtering**: Automatically excludes private members (prefixed with `_`) to Core capabilities include:
ensure clean public documentation.
- **Module discovery** Locate Python modules and packages within a project.
- **Static introspection** Parse docstrings, signatures, and object
hierarchies using the ``griffe`` library without executing the code.
- **Public API filtering** Exclude private members (names prefixed with
``_``) to produce clean public documentation structures.
""" """
from .griffe_loader import GriffeLoader, discover_module_paths from .griffe_loader import GriffeLoader, discover_module_paths

View File

@@ -1,6 +1,9 @@
""" """
This module provides the GriffeLoader, which uses the 'griffe' library to Utilities for loading and introspecting Python modules using Griffe.
introspect Python source code and populate the doc-forge Project models.
This module provides the ``GriffeLoader`` class and helper utilities used to
discover Python modules, introspect their structure, and convert the results
into doc-forge documentation models.
""" """
import logging import logging
@@ -25,19 +28,27 @@ def discover_module_paths(
project_root: Path | None = None, project_root: Path | None = None,
) -> List[str]: ) -> List[str]:
""" """
Discover all Python modules under a package via filesystem traversal. Discover Python modules within a package directory.
Rules: The function scans the filesystem for ``.py`` files inside the specified
- Directory with __init__.py is treated as a package. package and converts them into dotted module import paths.
- Any .py file is treated as a module.
- All paths are converted to dotted module paths. Discovery rules:
- Directories containing ``__init__.py`` are treated as packages.
- Each ``.py`` file is treated as a module.
- Results are returned as dotted import paths.
Args: Args:
module_name: The name of the package to discover. module_name: Top-level package name to discover modules from.
project_root: The root directory of the project. Defaults to current working directory. project_root: Root directory used to resolve module paths. If not
provided, the current working directory is used.
Returns: Returns:
A sorted list of dotted module paths. A sorted list of unique dotted module import paths.
Raises:
FileNotFoundError: If the specified package directory does not exist.
""" """
if project_root is None: if project_root is None:
@@ -64,12 +75,19 @@ def discover_module_paths(
class GriffeLoader: class GriffeLoader:
""" """
Loads Python modules and extracts documentation using the Griffe introspection engine. Load Python modules using Griffe and convert them into doc-forge models.
This loader uses the Griffe introspection engine to analyze Python source
code and transform the extracted information into ``Project``, ``Module``,
and ``DocObject`` instances used by doc-forge.
""" """
def __init__(self) -> None: def __init__(self) -> None:
""" """
Initialize the GriffeLoader. Initialize the Griffe-backed loader.
Creates an internal Griffe loader instance with dedicated collections
for modules and source lines.
""" """
self._loader = _GriffeLoader( self._loader = _GriffeLoader(
modules_collection=ModulesCollection(), modules_collection=ModulesCollection(),
@@ -83,15 +101,26 @@ class GriffeLoader:
skip_import_errors: bool = None, skip_import_errors: bool = None,
) -> Project: ) -> Project:
""" """
Load multiple modules and combine them into a single Project models. Load multiple modules and assemble them into a Project model.
Each module path is introspected and converted into a ``Module``
instance. All modules are then aggregated into a single ``Project``
object.
Args: Args:
module_paths: A list of dotted paths to the modules to load. module_paths: List of dotted module import paths to load.
project_name: Optional name for the project. Defaults to the first module name. project_name: Optional override for the project name. Defaults
skip_import_errors: If True, modules that fail to import will be skipped. to the top-level name of the first module.
skip_import_errors: If True, modules that fail to load will be
skipped instead of raising an error.
Returns: Returns:
A Project instance containing the loaded modules. A populated ``Project`` instance containing the loaded modules.
Raises:
ValueError: If no module paths are provided.
ImportError: If a module fails to load and
``skip_import_errors`` is False.
""" """
if not module_paths: if not module_paths:
raise ValueError("At least one module path must be provided") raise ValueError("At least one module path must be provided")
@@ -116,13 +145,16 @@ class GriffeLoader:
def load_module(self, path: str) -> Module: def load_module(self, path: str) -> Module:
""" """
Load a single module and convert its introspection data into the docforge models. Load and convert a single Python module.
The module is introspected using Griffe and then transformed into
a doc-forge ``Module`` model.
Args: Args:
path: The dotted path of the module to load. path: Dotted import path of the module.
Returns: Returns:
A Module instance. A populated ``Module`` instance.
""" """
self._loader.load(path) self._loader.load(path)
griffe_module = self._loader.modules_collection[path] griffe_module = self._loader.modules_collection[path]
@@ -135,13 +167,16 @@ class GriffeLoader:
def _convert_module(self, obj: Object) -> Module: def _convert_module(self, obj: Object) -> Module:
""" """
Convert a Griffe Object (module) into a docforge Module. Convert a Griffe module object into a doc-forge Module.
All public members of the module are recursively converted into
``DocObject`` instances.
Args: Args:
obj: The Griffe Object representing the module. obj: Griffe object representing the module.
Returns: Returns:
A populated Module instance. A populated ``Module`` model.
""" """
module = Module( module = Module(
path=obj.path, path=obj.path,
@@ -158,13 +193,17 @@ class GriffeLoader:
def _convert_object(self, obj: Object) -> DocObject: def _convert_object(self, obj: Object) -> DocObject:
""" """
Recursively convert a Griffe Object into a DocObject hierarchy. Convert a Griffe object into a doc-forge DocObject.
The conversion preserves the object's metadata such as name,
kind, path, signature, and docstring. Child members are processed
recursively.
Args: Args:
obj: The Griffe Object to convert. obj: Griffe object representing a documented Python object.
Returns: Returns:
A DocObject instance. A ``DocObject`` instance representing the converted object.
""" """
kind = obj.kind.value kind = obj.kind.value
signature = self._safe_signature(obj) signature = self._safe_signature(obj)
@@ -193,13 +232,13 @@ class GriffeLoader:
def _safe_docstring(self, obj: Object) -> Optional[str]: def _safe_docstring(self, obj: Object) -> Optional[str]:
""" """
Safely retrieve the docstring value from a Griffe object. Safely extract a docstring from a Griffe object.
Args: Args:
obj: The Griffe Object to inspect. obj: Griffe object to inspect.
Returns: Returns:
The raw docstring string, or None if missing or unresolvable. The raw docstring text if available, otherwise ``None``.
""" """
try: try:
return obj.docstring.value if obj.docstring else None return obj.docstring.value if obj.docstring else None
@@ -208,13 +247,14 @@ class GriffeLoader:
def _safe_signature(self, obj: Object) -> Optional[str]: def _safe_signature(self, obj: Object) -> Optional[str]:
""" """
Safely retrieve the signature string from a Griffe object. Safely extract the signature of a Griffe object.
Args: Args:
obj: The Griffe Object to inspect. obj: Griffe object to inspect.
Returns: Returns:
The string representation of the signature, or None. String representation of the object's signature if available,
otherwise ``None``.
""" """
try: try:
if hasattr(obj, "signature") and obj.signature: if hasattr(obj, "signature") and obj.signature:

View File

@@ -1,17 +1,28 @@
""" """
# Model Layer Model layer for doc-forge.
The `docforge.models` package provides the core data structures used to represent The ``docforge.models`` package defines the core data structures used to
Python source code in a documentation-focused hierarchy. represent Python source code as a structured documentation model.
## Key Components Overview
--------
- **Project**: The root container for all documented modules. The model layer forms the central intermediate representation used throughout
- **Module**: Represents a Python module or package, containing members. doc-forge. Python modules and objects discovered during introspection are
- **DocObject**: A recursive structure for classes, functions, and attributes. converted into a hierarchy of documentation models that can later be rendered
into different documentation formats.
These classes are designed to be renderer-agnostic, allowing the same internal Key components:
representation to be transformed into various output formats (currently MkDocs).
- **Project** Root container representing an entire documented codebase.
- **Module** Representation of a Python module or package containing
documented members.
- **DocObject** Recursive structure representing Python objects such as
classes, functions, methods, and attributes.
These models are intentionally **renderer-agnostic**, allowing the same
documentation structure to be transformed into multiple output formats
(e.g., MkDocs, MCP, or other renderers).
""" """
from .project import Project from .project import Project

View File

@@ -1,7 +1,10 @@
""" """
This module defines the Module class, which represents a Python module or package Documentation model representing a Python module or package.
in the doc-forge documentation models. It acts as a container for top-level
documented objects. This module defines the ``Module`` class used in the doc-forge documentation
model. A ``Module`` acts as a container for top-level documented objects
(classes, functions, variables, and other members) discovered during
introspection.
""" """
from typing import Dict, Iterable, Optional from typing import Dict, Iterable, Optional
@@ -11,12 +14,17 @@ from docforge.models.object import DocObject
class Module: class Module:
""" """
Represents a documented Python module or package. Representation of a documented Python module or package.
A ``Module`` stores metadata about the module itself and maintains a
collection of top-level documentation objects discovered during
introspection.
Attributes: Attributes:
path: Dotted import path of the module. path: Dotted import path of the module.
docstring: Module-level docstring content. docstring: Module-level documentation string, if present.
members: Dictionary mapping object names to their DocObject representations. members: Mapping of object names to their corresponding
``DocObject`` representations.
""" """
def __init__( def __init__(
@@ -25,11 +33,11 @@ class Module:
docstring: Optional[str] = None, docstring: Optional[str] = None,
) -> None: ) -> None:
""" """
Initialize a new Module. Initialize a Module instance.
Args: Args:
path: The dotted path of the module. path: Dotted import path identifying the module.
docstring: The module's docstring, if any. docstring: Module-level documentation text, if available.
""" """
self.path = path self.path = path
self.docstring = docstring self.docstring = docstring
@@ -40,27 +48,32 @@ class Module:
Add a documented object to the module. Add a documented object to the module.
Args: Args:
obj: The object to add. obj: Documentation object to register as a top-level
member of the module.
""" """
self.members[obj.name] = obj self.members[obj.name] = obj
def get_object(self, name: str) -> DocObject: def get_object(self, name: str) -> DocObject:
""" """
Retrieve a member object by name. Retrieve a documented object by name.
Args: Args:
name: The name of the object. name: Name of the object to retrieve.
Returns: Returns:
The requested DocObject. The corresponding ``DocObject`` instance.
Raises:
KeyError: If no object with the given name exists.
""" """
return self.members[name] return self.members[name]
def get_all_objects(self) -> Iterable[DocObject]: def get_all_objects(self) -> Iterable[DocObject]:
""" """
Get all top-level objects in the module. Return all top-level documentation objects in the module.
Returns: Returns:
An iterable of DocObject instances. An iterable of ``DocObject`` instances representing the
module's public members.
""" """
return self.members.values() return self.members.values()

View File

@@ -1,7 +1,10 @@
""" """
This module defines the DocObject class, the fundamental recursive unit of the Documentation model representing individual Python objects.
doc-forge documentation models. A DocObject represents a single Python entity
(class, function, method, or attribute) and its nested members. This module defines the ``DocObject`` class, the fundamental recursive unit of
the doc-forge documentation model. Each ``DocObject`` represents a Python
entity such as a class, function, method, or attribute, and may contain nested
members that form a hierarchical documentation structure.
""" """
from typing import Dict, Iterable, Optional from typing import Dict, Iterable, Optional
@@ -9,15 +12,20 @@ from typing import Dict, Iterable, Optional
class DocObject: class DocObject:
""" """
Represents a documented Python object (class, function, method, etc.). Representation of a documented Python object.
A ``DocObject`` models a single Python entity discovered during
introspection. Objects may contain nested members, allowing the structure
of modules, classes, and other containers to be represented recursively.
Attributes: Attributes:
name: Local name of the object. name: Local name of the object.
kind: Type of object (e.g., 'class', 'function', 'attribute'). kind: Type of object (for example ``class``, ``function``,
path: Full dotted import path to the object. ``method``, or ``attribute``).
signature: Callable signature, if applicable. path: Fully qualified dotted path to the object.
docstring: Raw docstring content extracted from the source. signature: Callable signature if the object represents a callable.
members: Dictionary mapping member names to their DocObject representations. docstring: Raw docstring text extracted from the source code.
members: Mapping of member names to child ``DocObject`` instances.
""" """
def __init__( def __init__(
@@ -29,48 +37,54 @@ class DocObject:
docstring: Optional[str] = None, docstring: Optional[str] = None,
) -> None: ) -> None:
""" """
Initialize a new DocObject. Initialize a DocObject instance.
Args: Args:
name: The local name of the object. name: Local name of the object.
kind: The kind of object (e.g., 'class', 'function'). kind: Object type identifier (for example ``class`` or ``function``).
path: The full dotted path to the object. path: Fully qualified dotted path of the object.
signature: The object's signature (for callable objects). signature: Callable signature if applicable.
docstring: The object's docstring, if any. docstring: Documentation string associated with the object.
""" """
self.name = name self.name = name
self.kind = kind self.kind = kind
self.path = path self.path = path
self.signature = signature self.signature = signature
self.docstring = docstring self.docstring = docstring
self.members: Dict[str, 'DocObject'] = {} self.members: Dict[str, "DocObject"] = {}
def add_member(self, obj: 'DocObject') -> None: def add_member(self, obj: "DocObject") -> None:
""" """
Add a child member to this object (e.g., a method to a class). Add a child documentation object.
This is typically used when attaching methods to classes or
nested objects to their parent containers.
Args: Args:
obj: The child DocObject to add. obj: Documentation object to add as a member.
""" """
self.members[obj.name] = obj self.members[obj.name] = obj
def get_member(self, name: str) -> 'DocObject': def get_member(self, name: str) -> "DocObject":
""" """
Retrieve a child member by name. Retrieve a member object by name.
Args: Args:
name: The name of the member. name: Name of the member to retrieve.
Returns: Returns:
The requested DocObject. The corresponding ``DocObject`` instance.
Raises:
KeyError: If the member does not exist.
""" """
return self.members[name] return self.members[name]
def get_all_members(self) -> Iterable['DocObject']: def get_all_members(self) -> Iterable["DocObject"]:
""" """
Get all members of this object. Return all child members of the object.
Returns: Returns:
An iterable of child DocObject instances. An iterable of ``DocObject`` instances representing nested members.
""" """
return self.members.values() return self.members.values()

View File

@@ -1,6 +1,9 @@
""" """
This module defines the Project class, the top-level container for a documented Documentation model representing a project.
project. It aggregates multiple Module instances into a single named entity.
This module defines the ``Project`` class, the top-level container used by
doc-forge to represent a documented codebase. A ``Project`` aggregates multiple
modules and provides access to them through a unified interface.
""" """
from typing import Dict, Iterable from typing import Dict, Iterable
@@ -10,29 +13,32 @@ from docforge.models.module import Module
class Project: class Project:
""" """
Represents a documentation project, serving as a container for modules. Representation of a documentation project.
A ``Project`` serves as the root container for all modules discovered during
introspection. Each module is stored by its dotted import path.
Attributes: Attributes:
name: Name of the project. name: Name of the project.
modules: Dictionary mapping module paths to Module instances. modules: Mapping of module paths to ``Module`` instances.
""" """
def __init__(self, name: str) -> None: def __init__(self, name: str) -> None:
""" """
Initialize a new Project. Initialize a Project instance.
Args: Args:
name: The name of the project. name: Name used to identify the documentation project.
""" """
self.name = name self.name = name
self.modules: Dict[str, Module] = {} self.modules: Dict[str, Module] = {}
def add_module(self, module: Module) -> None: def add_module(self, module: Module) -> None:
""" """
Add a module to the project. Register a module in the project.
Args: Args:
module: The module to add. module: Module instance to add to the project.
""" """
self.modules[module.path] = module self.modules[module.path] = module
@@ -41,27 +47,30 @@ class Project:
Retrieve a module by its dotted path. Retrieve a module by its dotted path.
Args: Args:
path: The dotted path of the module (e.g., 'pkg.mod'). path: Fully qualified dotted module path (for example ``pkg.module``).
Returns: Returns:
The requested Module. The corresponding ``Module`` instance.
Raises:
KeyError: If the module does not exist in the project.
""" """
return self.modules[path] return self.modules[path]
def get_all_modules(self) -> Iterable[Module]: def get_all_modules(self) -> Iterable[Module]:
""" """
Get all modules in the project. Return all modules contained in the project.
Returns: Returns:
An iterable of Module objects. An iterable of ``Module`` instances.
""" """
return self.modules.values() return self.modules.values()
def get_module_list(self) -> list[str]: def get_module_list(self) -> list[str]:
""" """
Get the list of all module dotted paths. Return the list of module import paths.
Returns: Returns:
A list of module paths. A list containing the dotted paths of all modules in the project.
""" """
return list(self.modules.keys()) return list(self.modules.keys())

View File

@@ -1,17 +1,22 @@
""" """
# Navigation Layer Navigation layer for doc-forge.
The `docforge.nav` package manages the mapping between the logical documentation The ``docforge.nav`` package manages the relationship between the logical
structure and the physical files on disk. documentation structure defined by the user and the physical documentation
files generated on disk.
## Workflow Workflow
--------
1. **Spec Definition**: Users define navigation intent in `docforge.nav.yml`. 1. **Specification** Users define navigation intent in ``docforge.nav.yml``.
2. **Resolution**: `resolve_nav` matches patterns in the spec to generated `.md` files. 2. **Resolution** ``resolve_nav`` expands patterns and matches them against
3. **Emission**: `MkDocsNavEmitter` produces the final YAML structure for `mkdocs.yml`. generated Markdown files.
3. **Emission** ``MkDocsNavEmitter`` converts the resolved structure into
the YAML navigation format required by ``mkdocs.yml``.
This abstraction allows doc-forge to support complex grouping and ordering This layer separates documentation organization from the underlying source
independently of the source code's physical layout. code layout, enabling flexible grouping, ordering, and navigation structures
independent of module hierarchy.
""" """
from .spec import NavSpec, load_nav_spec from .spec import NavSpec, load_nav_spec

View File

@@ -1,7 +1,9 @@
""" """
This module provides the MkDocsNavEmitter, which converts a ResolvedNav instance MkDocs navigation emitter.
into the specific YAML-ready list structure expected by the MkDocs 'nav'
configuration. This module provides the ``MkDocsNavEmitter`` class, which converts a
``ResolvedNav`` instance into the navigation structure required by the
MkDocs ``nav`` configuration.
""" """
from pathlib import Path from pathlib import Path
@@ -12,19 +14,24 @@ from docforge.nav.resolver import ResolvedNav
class MkDocsNavEmitter: class MkDocsNavEmitter:
""" """
Emitter responsible for transforming a ResolvedNav into an MkDocs-compatible Emit MkDocs navigation structures from resolved navigation data.
navigation structure.
The emitter transforms a ``ResolvedNav`` object into the YAML-compatible
list structure expected by the MkDocs ``nav`` configuration field.
""" """
def emit(self, nav: ResolvedNav) -> List[Dict[str, Any]]: def emit(self, nav: ResolvedNav) -> List[Dict[str, Any]]:
""" """
Generate a list of navigation entries for mkdocs.yml. Generate a navigation structure for ``mkdocs.yml``.
Args: Args:
nav: The resolved navigation data. nav: Resolved navigation data describing documentation groups
and their associated Markdown files.
Returns: Returns:
A list of dictionary mappings representing the MkDocs navigation. A list of dictionaries representing the MkDocs navigation layout.
Each dictionary maps a navigation label to a page or a list of
pages.
""" """
result: List[Dict[str, Any]] = [] result: List[Dict[str, Any]] = []
@@ -45,16 +52,18 @@ class MkDocsNavEmitter:
def _to_relative(self, path: Path, docs_root: Path | None) -> str: def _to_relative(self, path: Path, docs_root: Path | None) -> str:
""" """
Convert a filesystem path to a path relative to the documentation root. Convert a filesystem path into a documentation-relative path.
This handles both absolute and relative filesystem paths, ensuring the
output is compatible with MkDocs navigation requirements. This method normalizes paths so they can be used in MkDocs navigation.
It handles both absolute and relative filesystem paths and ensures the
resulting path is relative to the documentation root.
Args: Args:
path: The path to convert. path: Filesystem path to convert.
docs_root: The root directory for documentation. docs_root: Root directory of the documentation sources.
Returns: Returns:
A string representing the relative POSIX-style path. POSIX-style path relative to the documentation root.
""" """
if docs_root and path.is_absolute(): if docs_root and path.is_absolute():
try: try:

View File

@@ -1,7 +1,8 @@
""" """
This module contains the logic for resolving a NavSpec against the physical Navigation resolution utilities.
filesystem. It expands globs and validates that all referenced documents
actually exist on disk. This module resolves a ``NavSpec`` against the filesystem by expanding glob
patterns and validating that referenced documentation files exist.
""" """
from pathlib import Path from pathlib import Path
@@ -14,12 +15,15 @@ from docforge.nav.spec import NavSpec
class ResolvedNav: class ResolvedNav:
""" """
Represents a navigation structure where all patterns and paths have been Resolved navigation structure.
resolved against the actual filesystem contents.
A ``ResolvedNav`` represents navigation data after glob patterns have been
expanded and paths validated against the filesystem.
Attributes: Attributes:
home: Resolved relative path to the home page. home: Relative path to the documentation home page.
groups: Mapping of group titles to lists of absolute or relative Path objects. groups: Mapping of navigation group titles to lists of resolved
documentation file paths.
""" """
def __init__( def __init__(
@@ -32,9 +36,9 @@ class ResolvedNav:
Initialize a ResolvedNav instance. Initialize a ResolvedNav instance.
Args: Args:
home: The relative path to the project home page. home: Relative path to the home page within the documentation root.
groups: A mapping of group names to their resolved filesystem paths. groups: Mapping of group titles to resolved documentation file paths.
docs_root: The root documentation directory. docs_root: Root directory of the documentation source files.
""" """
self.home = home self.home = home
self.groups = groups self.groups = groups
@@ -42,15 +46,20 @@ class ResolvedNav:
def all_files(self) -> Iterable[Path]: def all_files(self) -> Iterable[Path]:
""" """
Get an iterable of all resolved files in the navigation structure. Iterate over all files referenced by the navigation structure.
Returns: Returns:
An iterable of Path objects. An iterable of ``Path`` objects representing documentation files.
Raises:
RuntimeError: If the home page is defined but the documentation
root is not available for resolution.
""" """
if self.home: if self.home:
if self._docs_root is None: if self._docs_root is None:
raise RuntimeError("docs_root is required to resolve home path") raise RuntimeError("docs_root is required to resolve home path")
yield self._docs_root / self.home yield self._docs_root / self.home
for paths in self.groups.values(): for paths in self.groups.values():
for p in paths: for p in paths:
yield p yield p
@@ -61,34 +70,37 @@ def resolve_nav(
docs_root: Path, docs_root: Path,
) -> ResolvedNav: ) -> ResolvedNav:
""" """
Create a ResolvedNav by processing a NavSpec against the filesystem. Resolve a navigation specification against the filesystem.
This expands globs and validates the existence of referenced files.
The function expands glob patterns defined in a ``NavSpec`` and verifies
that referenced documentation files exist within the documentation root.
Args: Args:
spec: The navigation specification to resolve. spec: Navigation specification describing documentation layout.
docs_root: The root directory for documentation files. docs_root: Root directory containing documentation Markdown files.
Returns: Returns:
A ResolvedNav instance. A ``ResolvedNav`` instance containing validated navigation paths.
Raises: Raises:
FileNotFoundError: If a pattern doesn't match any files or the docs_root doesn't exist. FileNotFoundError: If the documentation root does not exist or a
navigation pattern does not match any files.
""" """
if not docs_root.exists(): if not docs_root.exists():
raise FileNotFoundError(docs_root) raise FileNotFoundError(docs_root)
def resolve_pattern(pattern: str) -> List[Path]: def resolve_pattern(pattern: str) -> List[Path]:
""" """
Resolve a single glob pattern relative to the docs_root. Resolve a glob pattern relative to the documentation root.
Args: Args:
pattern: The glob pattern to resolve. pattern: Glob pattern used to match documentation files.
Returns: Returns:
A sorted list of matching Path objects. A sorted list of matching ``Path`` objects.
Raises: Raises:
FileNotFoundError: If the pattern doesn't match any files. FileNotFoundError: If the pattern does not match any files.
""" """
full = docs_root / pattern full = docs_root / pattern
matches = sorted( matches = sorted(
@@ -100,7 +112,7 @@ def resolve_nav(
return matches return matches
# Resolve home # Resolve home page
home: str | None = None home: str | None = None
if spec.home: if spec.home:
home_path = docs_root / spec.home home_path = docs_root / spec.home
@@ -108,7 +120,7 @@ def resolve_nav(
raise FileNotFoundError(spec.home) raise FileNotFoundError(spec.home)
home = spec.home home = spec.home
# Resolve groups # Resolve navigation groups
resolved_groups: Dict[str, List[Path]] = {} resolved_groups: Dict[str, List[Path]] = {}
for group, patterns in spec.groups.items(): for group, patterns in spec.groups.items():

View File

@@ -1,7 +1,9 @@
""" """
This module defines the NavSpec class, which represents the user's intent for Navigation specification model.
documentation navigation as defined in a YAML specification (usually
docforge.nav.yml). This module defines the ``NavSpec`` class, which represents the navigation
structure defined by the user in the doc-forge navigation specification
(typically ``docforge.nav.yml``).
""" """
from pathlib import Path from pathlib import Path
@@ -12,11 +14,16 @@ import yaml
class NavSpec: class NavSpec:
""" """
Parsed representation of the docforge navigation specification file. Parsed representation of a navigation specification.
A ``NavSpec`` describes the intended documentation navigation layout before
it is resolved against the filesystem.
Attributes: Attributes:
home: Path to the home document (e.g., 'index.md'). home: Relative path to the documentation home page (for example
groups: Mapping of group titles to lists of path patterns/globs. ``index.md``).
groups: Mapping of navigation group titles to lists of file patterns
or glob expressions.
""" """
def __init__( def __init__(
@@ -25,11 +32,12 @@ class NavSpec:
groups: Dict[str, List[str]], groups: Dict[str, List[str]],
) -> None: ) -> None:
""" """
Initialize a NavSpec. Initialize a NavSpec instance.
Args: Args:
home: The path to the home document. home: Relative path to the home document.
groups: A mapping of group names to lists of path patterns (globs). groups: Mapping of group names to lists of path patterns
(glob expressions).
""" """
self.home = home self.home = home
self.groups = groups self.groups = groups
@@ -37,17 +45,18 @@ class NavSpec:
@classmethod @classmethod
def load(cls, path: Path) -> "NavSpec": def load(cls, path: Path) -> "NavSpec":
""" """
Load a NavSpec from a YAML file. Load a navigation specification from a YAML file.
Args: Args:
path: The filesystem path to the YAML file. path: Filesystem path to the navigation specification file.
Returns: Returns:
A NavSpec instance. A ``NavSpec`` instance representing the parsed configuration.
Raises: Raises:
FileNotFoundError: If the path does not exist. FileNotFoundError: If the specified file does not exist.
ValueError: If the file content is not a valid NavSpec mapping. ValueError: If the file contents are not a valid navigation
specification.
""" """
if not path.exists(): if not path.exists():
raise FileNotFoundError(path) raise FileNotFoundError(path)
@@ -78,33 +87,45 @@ class NavSpec:
def all_patterns(self) -> List[str]: def all_patterns(self) -> List[str]:
""" """
Get all path patterns referenced in the specification. Return all path patterns referenced by the specification.
Returns: Returns:
A list of all patterns (home plus all groups). A list containing the home document (if defined) and all
group pattern entries.
""" """
patterns: List[str] = [] patterns: List[str] = []
if self.home: if self.home:
patterns.append(self.home) patterns.append(self.home)
for items in self.groups.values(): for items in self.groups.values():
patterns.extend(items) patterns.extend(items)
return patterns return patterns
def load_nav_spec(path: Path) -> NavSpec: def load_nav_spec(path: Path) -> NavSpec:
""" """
Utility function to load a NavSpec from a file. Load a navigation specification file.
This helper function reads a YAML navigation file and constructs a
corresponding ``NavSpec`` instance.
Args: Args:
path: Path to the navigation specification file. path: Path to the navigation specification file.
Returns: Returns:
A loaded NavSpec instance. A ``NavSpec`` instance representing the parsed specification.
Raises:
FileNotFoundError: If the specification file does not exist.
ValueError: If the YAML structure is invalid.
""" """
if not path.exists(): if not path.exists():
raise FileNotFoundError(path) raise FileNotFoundError(path)
data = yaml.safe_load(path.read_text(encoding="utf-8")) data = yaml.safe_load(path.read_text(encoding="utf-8"))
if not isinstance(data, dict): if not isinstance(data, dict):
raise ValueError("Nav spec must be a YAML mapping") raise ValueError("Nav spec must be a YAML mapping")

View File

@@ -1,20 +1,28 @@
""" """
# Renderers Layer Renderers layer for doc-forge.
The `docforge.renderers` package handles the transformation of the internal The ``docforge.renderers`` package transforms the internal documentation
documentation models into physical files formatted for specific documentation models into files formatted for specific documentation systems.
engines.
## Current Implementations Overview
--------
- **MkDocsRenderer**: Generates Markdown files utilizing the `mkdocstrings` Renderers consume the doc-forge project model and generate output suitable
syntax. It automatically handles package/module hierarchy and generates for documentation tools or machine interfaces.
`index.md` files for packages.
## Extending Current implementations:
To add a new renderer, implement the `DocRenderer` protocol defined in - **MkDocsRenderer** Produces Markdown files compatible with MkDocs and
`docforge.renderers.base`. 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 from .mkdocs_renderer import MkDocsRenderer

View File

@@ -1,7 +1,9 @@
""" """
This module defines the base interfaces and configuration containers for Renderer base interfaces and configuration models.
doc-forge renderers. All renderer implementations should adhere to the
DocRenderer protocol. 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 from pathlib import Path
@@ -14,12 +16,22 @@ class RendererConfig:
""" """
Configuration container for documentation renderers. Configuration container for documentation renderers.
Args: A ``RendererConfig`` instance groups together the project model and the
out_dir: The directory where documentation files should be written. output directory used during rendering.
project: The introspected project models to be rendered.
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: 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.out_dir = out_dir
self.project = project self.project = project
@@ -27,6 +39,9 @@ class RendererConfig:
class DocRenderer(Protocol): class DocRenderer(Protocol):
""" """
Protocol defining the interface for documentation renderers. 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 name: str
@@ -37,10 +52,11 @@ class DocRenderer(Protocol):
out_dir: Path, out_dir: Path,
) -> None: ) -> None:
""" """
Generate renderer-specific source files for the given project. Generate renderer-specific documentation sources.
Args: Args:
project: The project models containing modules and objects. project: Project model containing modules and documentation objects.
out_dir: Target directory for the generated files. 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: 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" name = "mcp"
def generate_sources(self, project: Project, out_dir: Path) -> None: 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: Args:
project: The project model to render. project: Documentation project model to render.
out_dir: Target directory for the generated JSON files. out_dir: Directory where MCP resources will be written.
""" """
modules_dir = out_dir / "modules" modules_dir = out_dir / "modules"
modules_dir.mkdir(parents=True, exist_ok=True) modules_dir.mkdir(parents=True, exist_ok=True)
@@ -54,11 +61,11 @@ class MCPRenderer:
def _write_module(self, module: Module, modules_dir: Path) -> None: 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: Args:
module: The module instance to serialize. module: Module instance to serialize.
modules_dir: The directory where the module JSON file should be written. modules_dir: Directory where module JSON files are stored.
""" """
payload = { payload = {
"module": module.path, "module": module.path,
@@ -71,13 +78,13 @@ class MCPRenderer:
def _render_module(self, module: Module) -> Dict: 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: Args:
module: The module instance to render. module: Module instance to convert.
Returns: Returns:
A dictionary following the MCP documentation resource schema. Dictionary representing the module and its documented objects.
""" """
data: Dict = { data: Dict = {
"path": module.path, "path": module.path,
@@ -92,13 +99,13 @@ class MCPRenderer:
def _render_object(self, obj: DocObject) -> Dict: def _render_object(self, obj: DocObject) -> Dict:
""" """
Recursively render a DocObject into structured MCP data. Recursively convert a DocObject into structured MCP data.
Args: Args:
obj: The documented object (class, func, etc.) to render. obj: Documentation object to convert.
Returns: Returns:
A dictionary representing the object and its members. Dictionary describing the object and any nested members.
""" """
data: Dict = { data: Dict = {
"name": obj.name, "name": obj.name,
@@ -119,4 +126,13 @@ class MCPRenderer:
@staticmethod @staticmethod
def _json(data: Dict) -> str: 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) return json.dumps(data, indent=2, ensure_ascii=False)

View File

@@ -1,13 +1,16 @@
""" """
MkDocsRenderer MkDocs renderer implementation.
Generates Markdown source files compatible with MkDocs Material This module defines the ``MkDocsRenderer`` class, which generates Markdown
and mkdocstrings, ensuring: documentation sources compatible with MkDocs Material and the mkdocstrings
plugin.
- Root index.md always exists The renderer ensures a consistent documentation structure by:
- Parent package indexes are created automatically
- Child modules are linked in parent index files - Creating a root ``index.md`` if one does not exist
- README.md can be generated from the root package docstring - 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 from pathlib import Path
@@ -16,8 +19,10 @@ from docforge.models import Project, Module
class MkDocsRenderer: class MkDocsRenderer:
""" """
Renderer that generates Markdown source files formatted for the MkDocs Renderer that produces Markdown documentation for MkDocs.
'mkdocstrings' plugin.
Generated pages use mkdocstrings directives to reference Python modules,
allowing MkDocs to render API documentation dynamically.
""" """
name = "mkdocs" name = "mkdocs"
@@ -25,6 +30,7 @@ class MkDocsRenderer:
# ------------------------- # -------------------------
# Public API # Public API
# ------------------------- # -------------------------
def generate_sources( def generate_sources(
self, self,
project: Project, project: Project,
@@ -32,18 +38,17 @@ class MkDocsRenderer:
module_is_source: bool | None = None, module_is_source: bool | None = None,
) -> None: ) -> None:
""" """
Produce a set of Markdown files in the output directory based on the Generate Markdown documentation files for a project.
provided Project models.
This method renders a documentation structure from the provided
project model and writes the resulting Markdown files to the
specified output directory.
Args: Args:
project: project: Project model containing modules to document.
The project model to render. out_dir: Directory where generated Markdown files will be written.
module_is_source: If True, treat the specified module as the
out_dir: documentation root rather than nesting it inside a folder.
Target directory for generated Markdown.
module_is_source:
If True, treat the module as the root folder.
""" """
out_dir.mkdir(parents=True, exist_ok=True) out_dir.mkdir(parents=True, exist_ok=True)
self._ensure_root_index(project, out_dir) self._ensure_root_index(project, out_dir)
@@ -51,7 +56,7 @@ class MkDocsRenderer:
modules = list(project.get_all_modules()) modules = list(project.get_all_modules())
paths = {m.path for m in modules} paths = {m.path for m in modules}
# Package detection (level-agnostic) # Detect packages (modules with children)
packages = { packages = {
p for p in paths p for p in paths
if any(other.startswith(p + ".") for other in paths) if any(other.startswith(p + ".") for other in paths)
@@ -72,22 +77,23 @@ class MkDocsRenderer:
module_is_source: bool | None = None, module_is_source: bool | None = None,
) -> None: ) -> None:
""" """
Generate README.md from the root package docstring. Generate a ``README.md`` file from the root module docstring.
Behavior: Behavior:
- If module_is_source is True: - If ``module_is_source`` is True, ``README.md`` is written to the
README.md is generated at project root (docs_dir.parent) project root directory.
- If False, README generation is currently not implemented.
- If module_is_source is False: Args:
TODO: generate README.md inside respective module folders 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.
""" """
# -------------------------
# Only implement source-root mode
# -------------------------
if not module_is_source: if not module_is_source:
# TODO: support per-module README generation # Future: support README generation per module
return return
readme_path = docs_dir.parent / "README.md" readme_path = docs_dir.parent / "README.md"
@@ -127,7 +133,13 @@ class MkDocsRenderer:
def _find_root_module(self, project: Project) -> Module | None: def _find_root_module(self, project: Project) -> Module | None:
""" """
Find the root module matching the project name. 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(): for module in project.get_all_modules():
if module.path == project.name: if module.path == project.name:
@@ -142,14 +154,18 @@ class MkDocsRenderer:
module_is_source: bool | None = None, module_is_source: bool | None = None,
) -> None: ) -> None:
""" """
Write a single module's documentation file. Packages are written as Write documentation for a single module.
'index.md' inside their respective directories.
Package modules are rendered as ``index.md`` files inside their
corresponding directories, while leaf modules are written as
standalone Markdown pages.
Args: Args:
module: The module to write. module: Module to render.
packages: A set of module paths that are identified as packages. packages: Set of module paths identified as packages.
out_dir: The base output directory. out_dir: Base directory for generated documentation files.
module_is_source: Module is the source folder and to be treated as the root folder. module_is_source: Whether the module acts as the documentation
root directory.
""" """
parts = module.path.split(".") parts = module.path.split(".")
@@ -160,15 +176,12 @@ class MkDocsRenderer:
module_name, parts = parts[0], parts module_name, parts = parts[0], parts
if module.path in packages: if module.path in packages:
# Package → directory/index.md
dir_path = out_dir.joinpath(*parts) dir_path = out_dir.joinpath(*parts)
dir_path.mkdir(parents=True, exist_ok=True) dir_path.mkdir(parents=True, exist_ok=True)
md_path = dir_path / "index.md" md_path = dir_path / "index.md"
link_target = f"{parts[-1]}/" if parts else None link_target = f"{parts[-1]}/" if parts else None
else: else:
# Leaf module → parent_dir/<name>.md
dir_path = out_dir.joinpath(*parts[:-1]) dir_path = out_dir.joinpath(*parts[:-1])
dir_path.mkdir(parents=True, exist_ok=True) dir_path.mkdir(parents=True, exist_ok=True)
@@ -186,14 +199,14 @@ class MkDocsRenderer:
def _render_markdown(self, title: str, module_path: str) -> str: 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: Args:
title: The display title for the page. title: Page title displayed in the documentation.
module_path: The dotted path of the module to document. module_path: Dotted import path of the module.
Returns: Returns:
A string containing the Markdown source. Markdown source containing a mkdocstrings directive.
""" """
return ( return (
f"# {title}\n\n" f"# {title}\n\n"
@@ -205,6 +218,13 @@ class MkDocsRenderer:
project: Project, project: Project,
out_dir: Path, out_dir: Path,
) -> None: ) -> 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" root_index = out_dir / "index.md"
if not root_index.exists(): if not root_index.exists():
@@ -221,6 +241,16 @@ class MkDocsRenderer:
link_target: str, link_target: str,
title: str, title: str,
) -> None: ) -> 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: if len(parts) == 1:
parent_index = out_dir / "index.md" parent_index = out_dir / "index.md"
else: else:

View File

@@ -1,3 +1,11 @@
"""
Server layer for doc-forge.
This module exposes server implementations used to provide live access
to generated documentation resources. Currently, it includes the MCP
documentation server.
"""
from .mcp_server import MCPServer from .mcp_server import MCPServer
__all__ = [ __all__ = [

View File

@@ -9,16 +9,21 @@ from mcp.server.fastmcp import FastMCP
class MCPServer: class MCPServer:
""" """
MCP server for serving a pre-built MCP documentation bundle. MCP server for serving a pre-generated documentation bundle.
The server exposes documentation resources and diagnostic tools through
MCP endpoints backed by JSON files generated by the MCP renderer.
""" """
def __init__(self, mcp_root: Path, name: str) -> None: def __init__(self, mcp_root: Path, name: str) -> None:
""" """
Initialize the MCPServer. Initialize the MCP server.
Args: Args:
mcp_root: Path to the directory containing pre-built MCP JSON resources. mcp_root: Directory containing the generated MCP documentation
name: Name of the MCP server. bundle (for example ``index.json``, ``nav.json``, and
``modules/``).
name: Identifier used for the MCP server instance.
""" """
self.mcp_root = mcp_root self.mcp_root = mcp_root
self.app = FastMCP(name) self.app = FastMCP(name)
@@ -32,19 +37,24 @@ class MCPServer:
def _read_json(self, path: Path) -> Any: def _read_json(self, path: Path) -> Any:
""" """
Read and parse a JSON file, returning diagnostic errors if missing. Load and parse a JSON file.
If the file does not exist, a structured error response is returned
instead of raising an exception.
Args: Args:
path: Path to the JSON file. path: Path to the JSON file to read.
Returns: Returns:
The parsed JSON data or an error dictionary. Parsed JSON data if the file exists, otherwise an error dictionary
describing the missing resource.
""" """
if not path.exists(): if not path.exists():
return { return {
"error": "not_found", "error": "not_found",
"path": str(path), "path": str(path),
} }
return json.loads(path.read_text(encoding="utf-8")) return json.loads(path.read_text(encoding="utf-8"))
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -53,8 +63,16 @@ class MCPServer:
def _register_resources(self) -> None: def _register_resources(self) -> None:
""" """
Register MCP resources for index, nav, and individual modules. Register MCP resource endpoints.
The server exposes documentation resources through the following
endpoints:
- ``docs://index`` Project metadata
- ``docs://nav`` Navigation structure
- ``docs://modules/{module}`` Individual module documentation
""" """
@self.app.resource("docs://index") @self.app.resource("docs://index")
def index(): def index():
return self._read_json(self.mcp_root / "index.json") return self._read_json(self.mcp_root / "index.json")
@@ -70,26 +88,35 @@ class MCPServer:
) )
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# MCP tools (optional / diagnostic) # MCP tools
# ------------------------------------------------------------------ # ------------------------------------------------------------------
def _register_tools(self) -> None: def _register_tools(self) -> None:
""" """
Register high-level MCP tools for diagnostics. Register optional MCP diagnostic tools.
These tools provide lightweight endpoints useful for verifying that
the MCP server is operational.
""" """
@self.app.tool() @self.app.tool()
def ping() -> str: def ping() -> str:
"""Return a simple health check response."""
return "pong" return "pong"
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# Server lifecycle # Server lifecycle
# ------------------------------------------------------------------ # ------------------------------------------------------------------
def run(self, transport: Literal["stdio", "sse", "streamable-http"] = "streamable-http") -> None: def run(
self,
transport: Literal["stdio", "sse", "streamable-http"] = "streamable-http",
) -> None:
""" """
Start the MCP server. Start the MCP server.
Args: Args:
transport: MCP transport (default: streamable-http) transport: Transport mechanism used by the MCP server. Supported
options include ``stdio``, ``sse``, and ``streamable-http``.
""" """
self.app.run(transport=transport) self.app.run(transport=transport)