All checks were successful
continuous-integration/drone/tag Build is passing
113 lines
3.0 KiB
Python
113 lines
3.0 KiB
Python
"""
|
|
This module contains the logic for resolving a NavSpec against the physical
|
|
filesystem. It expands globs and validates that all referenced documents
|
|
actually exist on disk.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from typing import Dict, Iterable, List
|
|
|
|
import glob
|
|
|
|
from docforge.nav.spec import NavSpec
|
|
|
|
|
|
class ResolvedNav:
|
|
"""
|
|
Represents a navigation structure where all patterns and paths have been
|
|
resolved against the actual filesystem contents.
|
|
|
|
Attributes:
|
|
home: Resolved relative path to the home page.
|
|
groups: Mapping of group titles to lists of absolute or relative Path objects.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
home: str | None,
|
|
groups: Dict[str, List[Path]],
|
|
docs_root: Path | None = None,
|
|
) -> None:
|
|
"""
|
|
Initialize a ResolvedNav instance.
|
|
|
|
Args:
|
|
home: The relative path to the project home page.
|
|
groups: A mapping of group names to their resolved filesystem paths.
|
|
docs_root: The root documentation directory.
|
|
"""
|
|
self.home = home
|
|
self.groups = groups
|
|
self._docs_root = docs_root
|
|
|
|
def all_files(self) -> Iterable[Path]:
|
|
"""
|
|
Get an iterable of all resolved files in the navigation structure.
|
|
|
|
Returns:
|
|
An iterable of Path objects.
|
|
"""
|
|
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:
|
|
"""
|
|
Create a ResolvedNav by processing a NavSpec against the filesystem.
|
|
This expands globs and validates the existence of referenced files.
|
|
|
|
Args:
|
|
spec: The navigation specification to resolve.
|
|
docs_root: The root directory for documentation files.
|
|
|
|
Returns:
|
|
A ResolvedNav instance.
|
|
|
|
Raises:
|
|
FileNotFoundError: If a pattern doesn't match any files or the docs_root doesn't exist.
|
|
"""
|
|
if not docs_root.exists():
|
|
raise FileNotFoundError(docs_root)
|
|
|
|
def resolve_pattern(pattern: str) -> List[Path]:
|
|
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
|
|
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 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,
|
|
)
|