From 5149034d192dfdc5009ff867a310eaacdd436ed4 Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Sat, 21 Feb 2026 21:03:49 +0530 Subject: [PATCH] module is source flag to ensure for single source modules it's not treated as a module but root --- docforge/cli/commands.py | 12 ++++++-- docforge/cli/commands.pyi | 1 + docforge/cli/mkdocs_utils.py | 14 +++++++-- docforge/cli/mkdocs_utils.pyi | 7 ++++- docforge/renderers/mkdocs_renderer.py | 40 ++++++++++++++++++++------ docforge/renderers/mkdocs_renderer.pyi | 12 +++++--- 6 files changed, 69 insertions(+), 17 deletions(-) diff --git a/docforge/cli/commands.py b/docforge/cli/commands.py index 335f818..12305a4 100644 --- a/docforge/cli/commands.py +++ b/docforge/cli/commands.py @@ -18,6 +18,7 @@ def cli() -> None: @cli.command() @click.option("--mcp", is_flag=True, help="Build MCP resources") @click.option("--mkdocs", is_flag=True, help="Build MkDocs site") +@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("--project-name", help="Project name override") # MkDocs specific @@ -32,6 +33,7 @@ def cli() -> None: def build( mcp: bool, mkdocs: bool, + module_is_source: bool, module: Optional[str], project_name: Optional[str], site_name: Optional[str], @@ -52,7 +54,8 @@ def build( Args: mcp: Use the MCP documentation builder. mkdocs: Use the MkDocs documentation builder. - module: The dotted path of the module to document. + module_is_source: Module is the source folder and to be treated as the root folder. + module: The dotted path of the module to the document. project_name: Optional override for the project name. site_name: (MkDocs) The site display name. Defaults to module name. docs_dir: (MkDocs) Target directory for Markdown sources. @@ -71,7 +74,12 @@ def build( site_name = module click.echo(f"Generating MkDocs sources in {docs_dir}...") - mkdocs_utils.generate_sources(module, project_name, docs_dir) + mkdocs_utils.generate_sources( + module, + docs_dir, + project_name, + module_is_source, + ) click.echo(f"Generating MkDocs config {mkdocs_yml}...") mkdocs_utils.generate_config(docs_dir, nav_file, template, mkdocs_yml, site_name) diff --git a/docforge/cli/commands.pyi b/docforge/cli/commands.pyi index df4f535..a38d851 100644 --- a/docforge/cli/commands.pyi +++ b/docforge/cli/commands.pyi @@ -7,6 +7,7 @@ cli: Group def build( mcp: bool, mkdocs: bool, + module_is_source: bool, module: Optional[str], project_name: Optional[str], site_name: Optional[str], diff --git a/docforge/cli/mkdocs_utils.py b/docforge/cli/mkdocs_utils.py index 5f9f68e..1c135bd 100644 --- a/docforge/cli/mkdocs_utils.py +++ b/docforge/cli/mkdocs_utils.py @@ -6,7 +6,12 @@ from docforge.loaders import GriffeLoader, discover_module_paths from docforge.renderers import MkDocsRenderer from docforge.nav import load_nav_spec, resolve_nav, MkDocsNavEmitter -def generate_sources(module: str, project_name: str | None, docs_dir: Path) -> None: +def generate_sources( + module: str, + docs_dir: Path, + project_name: str | None = None, + module_is_source: bool | None = None, +) -> None: """ Generate Markdown source files for the specified module. @@ -14,13 +19,18 @@ def generate_sources(module: str, project_name: str | None, docs_dir: Path) -> N module: The dotted path of the primary module to document. project_name: Optional override for the project name. 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. """ loader = GriffeLoader() discovered_paths = discover_module_paths(module) project = loader.load_project(discovered_paths, project_name) renderer = MkDocsRenderer() - renderer.generate_sources(project, docs_dir) + renderer.generate_sources( + project, + docs_dir, + module_is_source, + ) def generate_config(docs_dir: Path, nav_file: Path, template: Path | None, out: Path, site_name: str) -> None: """ diff --git a/docforge/cli/mkdocs_utils.pyi b/docforge/cli/mkdocs_utils.pyi index fc23e03..052bee9 100644 --- a/docforge/cli/mkdocs_utils.pyi +++ b/docforge/cli/mkdocs_utils.pyi @@ -1,6 +1,11 @@ from pathlib import Path -def generate_sources(module: str, project_name: str | None, docs_dir: Path) -> None: ... +def generate_sources( + module: str, + docs_dir: Path, + project_name: str | None = None, + module_is_source: bool | None = None, +) -> None: ... def generate_config(docs_dir: Path, nav_file: Path, template: Path | None, out: Path, site_name: str) -> None: ... def build(mkdocs_yml: Path) -> None: ... def serve(mkdocs_yml: Path) -> None: ... diff --git a/docforge/renderers/mkdocs_renderer.py b/docforge/renderers/mkdocs_renderer.py index 7bb49e6..b851812 100644 --- a/docforge/renderers/mkdocs_renderer.py +++ b/docforge/renderers/mkdocs_renderer.py @@ -10,7 +10,7 @@ and mkdocstrings, ensuring: """ from pathlib import Path -from docforge.models import Project +from docforge.models import Project, Module class MkDocsRenderer: @@ -24,7 +24,12 @@ class MkDocsRenderer: # ------------------------- # Public API # ------------------------- - def generate_sources(self, project: Project, out_dir: Path) -> None: + def generate_sources( + self, + project: Project, + out_dir: Path, + module_is_source: bool | None = None, + ) -> None: """ Produce a set of Markdown files in the output directory based on the provided Project models. @@ -32,6 +37,7 @@ class MkDocsRenderer: 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. """ out_dir.mkdir(parents=True, exist_ok=True) self._ensure_root_index(project, out_dir) @@ -46,12 +52,23 @@ class MkDocsRenderer: } for module in modules: - self._write_module(module, packages, out_dir) + self._write_module( + module, + packages, + out_dir, + module_is_source, + ) # ------------------------- # Internal helpers # ------------------------- - def _write_module(self, module, packages: set[str], out_dir: Path) -> None: + def _write_module( + self, + module: Module, + packages: set[str], + out_dir: Path, + module_is_source: bool | None = None, + ) -> None: """ Write a single module's documentation file. Packages are written as 'index.md' inside their respective directories. @@ -60,29 +77,36 @@ class MkDocsRenderer: 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. """ parts = module.path.split(".") + if module_is_source: + module_name, parts = parts[0], parts[1:] + else: + 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]}/" + link_target = f"{parts[-1]}/" if parts else None else: # Leaf module → parent_dir/.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" + link_target = f"{parts[-1]}.md" if parts else None - title = parts[-1].replace("_", " ").title() + title = parts[-1].replace("_", " ").title() if parts else module_name content = self._render_markdown(title, module.path) if not md_path.exists() or md_path.read_text(encoding="utf-8") != content: md_path.write_text(content, encoding="utf-8") - self._ensure_parent_index(parts, out_dir, link_target, title) + if not module_is_source: + self._ensure_parent_index(parts, out_dir, link_target, title) def _render_markdown(self, title: str, module_path: str) -> str: """ diff --git a/docforge/renderers/mkdocs_renderer.pyi b/docforge/renderers/mkdocs_renderer.pyi index 3b13d76..130e4dc 100644 --- a/docforge/renderers/mkdocs_renderer.pyi +++ b/docforge/renderers/mkdocs_renderer.pyi @@ -1,19 +1,23 @@ from pathlib import Path -from typing import Set - from docforge.models import Project, Module class MkDocsRenderer: name: str - def generate_sources(self, project: Project, out_dir: Path) -> None: ... + def generate_sources( + self, + project: Project, + out_dir: Path, + module_is_source: bool | None = None, + ) -> None: ... def _write_module( self, module: Module, - packages: Set[str], + packages: set[str], out_dir: Path, + module_is_source: bool | None = None, ) -> None: ... def _render_markdown(self, title: str, module_path: str) -> str: ...