feat: add wiki build kind with file-structure-derived navigation
- add build_wiki_nav deriving MkDocs nav from docs/wiki file structure (index.md -> Home, numeric prefixes stripped and title-cased, nested dirs become groups, natural ordering) - add --wiki / --wiki-dir to build; wiki-only builds need no --module - merge wiki nav before generated lib/api nav; wiki Home replaces the nav spec Home entry - add mkdocs.wiki.yml template fragment and nav/cli tests - dogfood doc-forge's own docs/wiki and regenerate site output
This commit is contained in:
@@ -29,6 +29,9 @@ def cli() -> None:
|
||||
@click.option("--mcp", is_flag=True, help="Build MCP resources")
|
||||
@click.option("--mkdocs", is_flag=True, help="Build MkDocs site")
|
||||
@click.option("--api", is_flag=True, help="Build API docs from an OpenAPI spec")
|
||||
@click.option(
|
||||
"--wiki", is_flag=True, help="Include a hand-written wiki in the MkDocs site"
|
||||
)
|
||||
@click.option(
|
||||
"--module-is-source",
|
||||
is_flag=True,
|
||||
@@ -48,6 +51,12 @@ def cli() -> None:
|
||||
default=Path("docs"),
|
||||
help="MkDocs documentation root",
|
||||
)
|
||||
@click.option(
|
||||
"--wiki-dir",
|
||||
type=click.Path(path_type=Path),
|
||||
default=Path("docs/wiki"),
|
||||
help="Hand-written wiki directory included in the MkDocs site",
|
||||
)
|
||||
@click.option(
|
||||
"--nav",
|
||||
"nav_file",
|
||||
@@ -74,12 +83,14 @@ def build(
|
||||
mcp: bool,
|
||||
mkdocs: bool,
|
||||
api: bool,
|
||||
wiki: bool,
|
||||
module_is_source: bool,
|
||||
module: str | None,
|
||||
openapi_spec: Path | None,
|
||||
project_name: str | None,
|
||||
site_name: str | None,
|
||||
docs_dir: Path,
|
||||
wiki_dir: Path,
|
||||
nav_file: Path,
|
||||
template: Path | None,
|
||||
mkdocs_yml: Path,
|
||||
@@ -96,6 +107,7 @@ def build(
|
||||
|
||||
- MkDocs static documentation sites for library reference docs
|
||||
- Swagger-enabled API docs generated from an OpenAPI spec
|
||||
- Hand-written wiki pages included in the MkDocs site
|
||||
- MCP structured documentation resources
|
||||
|
||||
Args:
|
||||
@@ -108,6 +120,9 @@ def build(
|
||||
api (bool):
|
||||
Enable API documentation generation from an OpenAPI spec.
|
||||
|
||||
wiki (bool):
|
||||
Include a hand-written wiki directory in the MkDocs site.
|
||||
|
||||
module_is_source (bool):
|
||||
Treat the specified module directory as the project root.
|
||||
|
||||
@@ -126,6 +141,9 @@ def build(
|
||||
docs_dir (Path):
|
||||
Shared documentation root used as the MkDocs ``docs_dir``.
|
||||
|
||||
wiki_dir (Path):
|
||||
Directory containing hand-written wiki markdown files.
|
||||
|
||||
nav_file (Path):
|
||||
Path to the navigation specification file.
|
||||
|
||||
@@ -142,13 +160,13 @@ def build(
|
||||
click.UsageError:
|
||||
If required options are missing or conflicting.
|
||||
"""
|
||||
if not mcp and not mkdocs and not api:
|
||||
raise click.UsageError("Must specify either --mcp, --mkdocs, or --api")
|
||||
if not mcp and not mkdocs and not api and not wiki:
|
||||
raise click.UsageError("Must specify either --mcp, --mkdocs, --wiki, or --api")
|
||||
|
||||
if api:
|
||||
if not openapi_spec:
|
||||
raise click.UsageError("--openapi-spec is required for API build")
|
||||
if site_name and not mkdocs:
|
||||
if site_name and not mkdocs and not wiki:
|
||||
raise click.UsageError(
|
||||
"--site-name cannot be overridden for API build; "
|
||||
"the OpenAPI spec provides the site name"
|
||||
@@ -181,16 +199,18 @@ def build(
|
||||
click.echo(f"Generating API sources in {api_dir}...")
|
||||
api_utils.generate_api_sources(spec, api_dir)
|
||||
|
||||
if mkdocs or api:
|
||||
if mkdocs or api or wiki:
|
||||
modes: list[str] = []
|
||||
if mkdocs:
|
||||
modes.append("lib")
|
||||
if api:
|
||||
modes.append("api")
|
||||
if wiki:
|
||||
modes.append("wiki")
|
||||
|
||||
site_description: str | None = None
|
||||
site_author: str | None = None
|
||||
effective_site_name = site_name or module
|
||||
effective_site_name = site_name or module or Path.cwd().name
|
||||
|
||||
if api:
|
||||
metadata = api_utils.derive_metadata(spec)
|
||||
@@ -208,6 +228,7 @@ def build(
|
||||
modes=modes,
|
||||
site_description=site_description,
|
||||
site_author=site_author,
|
||||
wiki_dir=wiki_dir if wiki else None,
|
||||
)
|
||||
|
||||
click.echo("Running MkDocs build...")
|
||||
|
||||
@@ -9,12 +9,14 @@ def build(
|
||||
mcp: bool,
|
||||
mkdocs: bool,
|
||||
api: bool,
|
||||
wiki: bool,
|
||||
module_is_source: bool,
|
||||
module: str | None,
|
||||
openapi_spec: Path | None,
|
||||
project_name: str | None,
|
||||
site_name: str | None,
|
||||
docs_dir: Path,
|
||||
wiki_dir: Path,
|
||||
nav_file: Path,
|
||||
template: Path | None,
|
||||
mkdocs_yml: Path,
|
||||
|
||||
@@ -13,7 +13,12 @@ import click
|
||||
import yaml
|
||||
|
||||
from docforge.loaders import GriffeLoader, discover_module_paths
|
||||
from docforge.nav import MkDocsNavEmitter, load_nav_spec, resolve_nav
|
||||
from docforge.nav import (
|
||||
MkDocsNavEmitter,
|
||||
build_wiki_nav,
|
||||
load_nav_spec,
|
||||
resolve_nav,
|
||||
)
|
||||
from docforge.renderers import MkDocsRenderer
|
||||
|
||||
|
||||
@@ -78,17 +83,20 @@ def generate_config(
|
||||
modes: Iterable[str] | None = None,
|
||||
site_description: str | None = None,
|
||||
site_author: str | None = None,
|
||||
wiki_dir: Path | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
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.
|
||||
specification (and, when a wiki directory is provided, from the wiki
|
||||
file structure).
|
||||
|
||||
The ``docs_dir`` is always written relative to the MkDocs root and is
|
||||
expected to be the shared documentation parent (for example ``docs``),
|
||||
with generated sources nested under ``lib/`` or ``api/`` subdirectories.
|
||||
with generated sources nested under ``lib/`` or ``api/`` subdirectories
|
||||
and hand-written wiki content under a ``wiki/`` subdirectory.
|
||||
|
||||
Args:
|
||||
docs_dir (Path):
|
||||
@@ -110,8 +118,9 @@ def generate_config(
|
||||
|
||||
modes (Optional[Iterable[str]]):
|
||||
Documentation modes to enable. Each mode contributes its own
|
||||
built-in template fragment (for example ``lib`` or ``api``),
|
||||
merged on top of the shared ``mkdocs.common.yml`` template.
|
||||
built-in template fragment (for example ``lib``, ``api``, or
|
||||
``wiki``), merged on top of the shared ``mkdocs.common.yml``
|
||||
template.
|
||||
|
||||
site_description (Optional[str]):
|
||||
Optional site description written into the configuration.
|
||||
@@ -119,16 +128,34 @@ def generate_config(
|
||||
site_author (Optional[str]):
|
||||
Optional site author written into the configuration.
|
||||
|
||||
wiki_dir (Optional[Path]):
|
||||
Optional path to a hand-written wiki directory (for example
|
||||
``docs/wiki``). When provided, the site navigation is derived
|
||||
from the wiki file structure and placed before the navigation
|
||||
groups defined in ``nav_file``.
|
||||
|
||||
Raises:
|
||||
click.FileError:
|
||||
If the navigation specification or template file cannot be found.
|
||||
If the navigation specification, template, or wiki directory
|
||||
cannot be found.
|
||||
"""
|
||||
if not nav_file.exists():
|
||||
if not nav_file.exists() and wiki_dir is None:
|
||||
raise click.FileError(str(nav_file), hint="Nav spec not found")
|
||||
|
||||
spec = load_nav_spec(nav_file)
|
||||
resolved = resolve_nav(spec, docs_dir)
|
||||
nav_block = MkDocsNavEmitter().emit(resolved)
|
||||
nav_block: list[dict] = []
|
||||
if nav_file.exists():
|
||||
spec = load_nav_spec(nav_file)
|
||||
resolved = resolve_nav(spec, docs_dir)
|
||||
nav_block = MkDocsNavEmitter().emit(resolved)
|
||||
else:
|
||||
spec = None
|
||||
|
||||
if wiki_dir is not None:
|
||||
if not wiki_dir.exists():
|
||||
raise click.FileError(str(wiki_dir), hint="Wiki dir not found")
|
||||
wiki_nav = build_wiki_nav(wiki_dir)
|
||||
if wiki_nav:
|
||||
nav_block = wiki_nav + [entry for entry in nav_block if "Home" not in entry]
|
||||
|
||||
data = _load_template(template, modes)
|
||||
|
||||
@@ -140,7 +167,7 @@ def generate_config(
|
||||
data["docs_dir"] = Path(os.path.relpath(docs_dir, out.parent)).as_posix()
|
||||
data["nav"] = nav_block
|
||||
|
||||
if spec.icon:
|
||||
if spec is not None and spec.icon:
|
||||
theme = data.setdefault("theme", {})
|
||||
if not isinstance(theme, dict):
|
||||
theme = {}
|
||||
|
||||
@@ -17,6 +17,7 @@ def generate_config(
|
||||
modes: Iterable[str] | None = None,
|
||||
site_description: str | None = None,
|
||||
site_author: str | None = None,
|
||||
wiki_dir: Path | None = None,
|
||||
) -> None: ...
|
||||
def build(mkdocs_yml: Path) -> None: ...
|
||||
def serve(mkdocs_yml: Path) -> None: ...
|
||||
|
||||
Reference in New Issue
Block a user