Files
doc-forge/docforge/nav/resolver.py

137 lines
3.8 KiB
Python

"""
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,
)