""" Navigation resolution utilities. This module resolves a ``NavSpec`` against the filesystem by expanding glob patterns and validating that referenced documentation files exist. """ from pathlib import Path from typing import Dict, Iterable, List import glob from docforge.nav.spec import NavSpec class ResolvedNav: """ Resolved navigation structure. A ``ResolvedNav`` represents navigation data after glob patterns have been expanded and paths validated against the filesystem. Attributes: home: Relative path to the documentation home page. groups: Mapping of navigation group titles to lists of resolved documentation file paths. """ def __init__( self, home: str | None, groups: Dict[str, List[Path]], docs_root: Path | None = None, ) -> None: """ Initialize a ResolvedNav instance. Args: home: Relative path to the home page within the documentation root. groups: Mapping of group titles to resolved documentation file paths. docs_root: Root directory of the documentation source files. """ self.home = home self.groups = groups self._docs_root = docs_root def all_files(self) -> Iterable[Path]: """ Iterate over all files referenced by the navigation structure. Returns: 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._docs_root is None: raise RuntimeError("docs_root is required to resolve home path") yield self._docs_root / self.home for paths in self.groups.values(): for p in paths: yield p def resolve_nav( spec: NavSpec, docs_root: Path, ) -> ResolvedNav: """ Resolve a navigation specification against the filesystem. The function expands glob patterns defined in a ``NavSpec`` and verifies that referenced documentation files exist within the documentation root. Args: spec: Navigation specification describing documentation layout. docs_root: Root directory containing documentation Markdown files. Returns: A ``ResolvedNav`` instance containing validated navigation paths. Raises: FileNotFoundError: If the documentation root does not exist or a navigation pattern does not match any files. """ if not docs_root.exists(): raise FileNotFoundError(docs_root) def resolve_pattern(pattern: str) -> List[Path]: """ Resolve a glob pattern relative to the documentation root. Args: pattern: Glob pattern used to match documentation files. Returns: A sorted list of matching ``Path`` objects. Raises: FileNotFoundError: If the pattern does not match any files. """ full = docs_root / pattern matches = sorted( Path(p) for p in glob.glob(str(full), recursive=True) ) if not matches: raise FileNotFoundError(pattern) return matches # Resolve home page home: str | None = None if spec.home: home_path = docs_root / spec.home if not home_path.exists(): raise FileNotFoundError(spec.home) home = spec.home # Resolve navigation groups resolved_groups: Dict[str, List[Path]] = {} for group, patterns in spec.groups.items(): files: List[Path] = [] for pattern in patterns: files.extend(resolve_pattern(pattern)) resolved_groups[group] = files return ResolvedNav( home=home, groups=resolved_groups, docs_root=docs_root, )