""" MkDocs navigation emitter. 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 typing import List, Dict, Any from docforge.nav.resolver import ResolvedNav class MkDocsNavEmitter: """ Emit MkDocs navigation structures from resolved navigation data. 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]]: """ Generate a navigation structure for ``mkdocs.yml``. Args: nav: Resolved navigation data describing documentation groups and their associated Markdown files. Returns: 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]] = [] # Home entry (semantic path) if nav.home: result.append({"Home": nav.home}) # Group entries for group, paths in nav.groups.items(): entries: List[str] = [] for p in paths: # Convert filesystem path back to docs-relative path rel_path = self._to_relative(p, nav._docs_root) entries.append(rel_path) result.append({group: entries}) return result def _to_relative(self, path: Path, docs_root: Path | None) -> str: """ Convert a filesystem path into a documentation-relative path. 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: path: Filesystem path to convert. docs_root: Root directory of the documentation sources. Returns: POSIX-style path relative to the documentation root. """ if docs_root and path.is_absolute(): try: path = path.relative_to(docs_root.resolve()) except ValueError: pass elif docs_root: # Handle relative paths (e.g. starting with 'docs/') path_str = path.as_posix() docs_root_str = docs_root.as_posix() if path_str.startswith(docs_root_str + "/"): return path_str[len(docs_root_str) + 1:] # Fallback for other cases return path.as_posix().split("/docs/", 1)[-1]