- fix GSDFC spec contradictions in __init__ docstring (parenthesized types, fenced-block rule) and sync generated README - rewrite docstrings across loaders, models, nav, servers, renderers, cli; sync .pyi stubs - add pydoclint (google style) gate to dev extras and pyproject config - fix mcp nav resources doc:// -> docs:// - refresh docs/lib and docs/mcp, drop stale docforge/ duplicate group - update wiki pages and add GSDFC + MCP guides under 05_development
151 lines
4.2 KiB
Python
151 lines
4.2 KiB
Python
"""
|
|
Navigation resolution utilities.
|
|
|
|
This module resolves a ``NavSpec`` against the filesystem by expanding glob
|
|
patterns and validating that referenced documentation files exist.
|
|
|
|
---
|
|
|
|
Notes:
|
|
- Glob resolution is recursive and returns paths in sorted order.
|
|
- Unmatched patterns raise ``FileNotFoundError`` to fail fast on typos.
|
|
|
|
---
|
|
"""
|
|
|
|
import glob
|
|
from collections.abc import Iterable
|
|
from pathlib import Path
|
|
|
|
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 (str | None):
|
|
Relative path to the home page within the documentation root.
|
|
groups (dict[str, list[Path]]):
|
|
Mapping of group titles to resolved documentation file paths.
|
|
docs_root (Path | None):
|
|
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.
|
|
|
|
Yields:
|
|
Path:
|
|
A documentation file referenced by the navigation, including
|
|
the home page when defined.
|
|
|
|
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():
|
|
yield from paths
|
|
|
|
|
|
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 (NavSpec):
|
|
Navigation specification describing documentation layout.
|
|
docs_root (Path):
|
|
Root directory containing documentation Markdown files.
|
|
|
|
Returns:
|
|
ResolvedNav:
|
|
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 (str):
|
|
Glob pattern used to match documentation files.
|
|
|
|
Returns:
|
|
list[Path]:
|
|
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,
|
|
)
|