""" # Summary Wiki navigation derivation. This module provides ``build_wiki_nav``, which derives an MkDocs-ready navigation block from the file structure of a hand-written wiki directory (typically ``docs/wiki``). wiki content is authored by hand and is never modified by doc-forge; only the navigation layout is inferred. # Notes - ``index.md`` at the wiki root becomes the ``Home`` entry. - Page labels are derived from filenames: numeric order prefixes such as ``01_`` or ``02-`` are stripped, separators are replaced with spaces, and names are title-cased (``01_overview.md`` becomes ``Overview``). - Subdirectories become nested navigation groups. A nested ``index.md`` is rendered as the section root placed first inside the group. - Only ``.md`` files are considered; hidden entries are ignored. """ import re from collections.abc import Callable from pathlib import Path from typing import Any def build_wiki_nav(wiki_dir: Path) -> list[dict[str, Any]]: """ Derive an MkDocs navigation block from a wiki directory. Returned paths are relative to the parent of ``wiki_dir`` and carry the wiki directory name as their leading component (for example ``wiki/01_overview.md`` when the wiki lives at ``docs/wiki``). This makes the result directly usable in an MkDocs ``nav`` block with - ``index.md`` at the wiki root becomes the ``Home`` entry. - Page labels are derived from filenames: numeric order prefixes such as ``01_`` or ``02-`` are stripped, separators are replaced with spaces, and names are title-cased (``01_overview.md`` becomes ``Overview``). - Subdirectories become nested navigation groups. A nested ``index.md`` is rendered as the section root placed first inside the group. - Only ``.md`` files are considered; hidden entries are ignored. Args: wiki_dir (Path): Path to the hand-written wiki directory, for example ``docs/wiki``. Returns: list[dict[str, Any]]: Navigation entries compatible with the MkDocs ``nav`` configuration. The list is empty if the wiki contains no Markdown files. Raises: FileNotFoundError: If the wiki directory does not exist. """ if not wiki_dir.exists(): raise FileNotFoundError(wiki_dir) root = wiki_dir.parent def rel(path: Path) -> str: try: return path.relative_to(root).as_posix() except ValueError: return path.as_posix() nav: list[dict[str, Any]] = [] if (wiki_dir / "index.md").exists(): nav.append({"Home": rel(wiki_dir / "index.md")}) nav.extend(_render_entries(wiki_dir, rel)) return nav def _render_entries( base_dir: Path, rel: Callable[[Path], str], ) -> list[dict[str, Any]]: """ Render navigation entries for the children of a wiki directory. Markdown pages become labeled entries, subdirectories containing Markdown become nested groups, and a nested ``index.md`` is emitted first as the section root. Args: base_dir (Path): Directory whose children are rendered. rel (Callable[[Path], str]): Callable converting a wiki file path into a docs-relative path. Returns: list[dict[str, Any]]: Navigation entries for ``base_dir`` in natural sort order. """ children = sorted( (child for child in base_dir.iterdir() if not child.name.startswith(".")), key=lambda child: _natural_key(child.name), ) entries: list[dict[str, Any]] = [] for child in children: if child.is_dir(): if not any(child.rglob("*.md")): continue group: list[dict[str, Any]] = [] nested_index = child / "index.md" if nested_index.exists(): group.append({_prettify(child.name): rel(nested_index)}) group.extend(_render_entries(child, rel)) entries.append({_prettify(child.name): group}) elif child.suffix == ".md": if child.name == "index.md": continue entries.append({_prettify(child.stem): rel(child)}) return entries def _prettify(name: str) -> str: """ Convert a filename or directory name into a navigation label. Numeric order prefixes (``01_``, ``02-``, ``03.``) are stripped, separators are replaced with spaces, and the result is title-cased. Args: name (str): Filename stem or directory name to prettify. Returns: str: Title-cased navigation label. """ name = re.sub(r"^\d+[_\-. ]?", "", name) name = re.sub(r"[_-]+", " ", name).strip() return name.title() def _natural_key(name: str) -> list[object]: """ Build a natural sort key from a filename. The key splits ``name`` into alternating non-digit and digit parts so that numeric prefixes sort numerically (``02`` before ``10``). Args: name (str): Filename or directory name to key. Returns: list[object]: Mixed list of lowercased strings and integers used for sorting. """ return [ int(part) if part.isdigit() else part.lower() for part in re.split(r"(\d+)", name) ]