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

1"""Port definitions — interfaces that infrastructure adapters must implement.""" 

2 

3from pathlib import Path 

4from typing import Protocol 

5 

6from smith.domain.value_objects import ( 

7 FileSpec, 

8 TemplateSource, 

9) 

10 

11 

12class TemplateSourceError(Exception): 

13 """Raised when a template source cannot be resolved.""" 

14 

15 

16class TemplateSourcePort(Protocol): 

17 """Interface for resolving template file specifications.""" 

18 

19 def resolve(self) -> list[FileSpec]: 

20 """Return the list of file specs from this template source.""" 

21 ... 

22 

23 def gitignore_patterns(self) -> list[str]: 

24 """Return the gitignore patterns for this template source.""" 

25 ... 

26 

27 

28class FileSystemPort(Protocol): 

29 """Interface for atomic file-system operations.""" 

30 

31 def check_conflicts(self, paths: list[Path]) -> list[Path]: 

32 """Return the subset of *paths* that already exist on disk.""" 

33 ... 

34 

35 def write_atomic(self, specs: list[FileSpec]) -> None: 

36 """Write all specs atomically; roll back on failure.""" 

37 ... 

38 

39 def remove(self, paths: list[Path]) -> None: 

40 """Remove the given paths from the project directory.""" 

41 ... 

42 

43 def exists(self, paths: list[Path]) -> dict[Path, bool]: 

44 """Return a mapping of each path to whether it exists on disk.""" 

45 ... 

46 

47 

48class GitignorePort(Protocol): 

49 """Interface for managing the smith-managed .gitignore section.""" 

50 

51 def add_section(self, patterns: list[str]) -> None: 

52 """Add or replace the smith-managed section in .gitignore.""" 

53 ... 

54 

55 def has_section(self) -> bool: 

56 """Return whether a smith-managed section exists in .gitignore.""" 

57 ... 

58 

59 def get_patterns(self) -> list[str]: 

60 """Return the patterns listed inside the smith-managed section.""" 

61 ... 

62 

63 

64class MetadataPort(Protocol): 

65 """Interface for persisting and loading template source metadata.""" 

66 

67 def save_source(self, source: TemplateSource) -> None: 

68 """Write the source identifier into the smith-managed section header.""" 

69 ... 

70 

71 def load_source(self) -> TemplateSource | None: 

72 """Read the source identifier from the smith-managed section header.""" 

73 ...