Coverage for smith / domain / ports.py: 100%
0 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-01 18:48 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-01 18:48 +0000
1"""Port definitions — interfaces that infrastructure adapters must implement."""
3from pathlib import Path
4from typing import Protocol
6from smith.domain.value_objects import (
7 FileSpec,
8 TemplateSource,
9)
12class TemplateSourceError(Exception):
13 """Raised when a template source cannot be resolved."""
16class TemplateSourcePort(Protocol):
17 """Interface for resolving template file specifications."""
19 def resolve(self) -> list[FileSpec]:
20 """Return the list of file specs from this template source."""
21 ...
23 def gitignore_patterns(self) -> list[str]:
24 """Return the gitignore patterns for this template source."""
25 ...
28class FileSystemPort(Protocol):
29 """Interface for atomic file-system operations."""
31 def check_conflicts(self, paths: list[Path]) -> list[Path]:
32 """Return the subset of *paths* that already exist on disk."""
33 ...
35 def write_atomic(self, specs: list[FileSpec]) -> None:
36 """Write all specs atomically; roll back on failure."""
37 ...
39 def remove(self, paths: list[Path]) -> None:
40 """Remove the given paths from the project directory."""
41 ...
43 def exists(self, paths: list[Path]) -> dict[Path, bool]:
44 """Return a mapping of each path to whether it exists on disk."""
45 ...
48class GitignorePort(Protocol):
49 """Interface for managing the smith-managed .gitignore section."""
51 def add_section(self, patterns: list[str]) -> None:
52 """Add or replace the smith-managed section in .gitignore."""
53 ...
55 def has_section(self) -> bool:
56 """Return whether a smith-managed section exists in .gitignore."""
57 ...
59 def get_patterns(self) -> list[str]:
60 """Return the patterns listed inside the smith-managed section."""
61 ...
64class MetadataPort(Protocol):
65 """Interface for persisting and loading template source metadata."""
67 def save_source(self, source: TemplateSource) -> None:
68 """Write the source identifier into the smith-managed section header."""
69 ...
71 def load_source(self) -> TemplateSource | None:
72 """Read the source identifier from the smith-managed section header."""
73 ...