pytest_beehave.bootstrap
Bootstrap logic for the features directory structure.
1"""Bootstrap logic for the features directory structure.""" 2 3from __future__ import annotations 4 5from dataclasses import dataclass 6from pathlib import Path 7 8_CANONICAL_SUBFOLDERS: tuple[str, ...] = ("backlog", "in-progress", "completed") 9 10 11@dataclass(frozen=True, slots=True) 12class BootstrapResult: 13 """Result of bootstrapping the features directory. 14 15 Attributes: 16 created_subfolders: Names of subfolders that were created. 17 migrated_files: Paths of files that were migrated. 18 collision_warnings: Warning messages for name collisions. 19 """ 20 21 created_subfolders: tuple[str, ...] 22 migrated_files: tuple[Path, ...] 23 collision_warnings: tuple[str, ...] 24 25 @property 26 def is_noop(self) -> bool: 27 """Return True if bootstrap made no changes.""" 28 return ( 29 len(self.created_subfolders) == 0 30 and len(self.migrated_files) == 0 31 and len(self.collision_warnings) == 0 32 ) 33 34 35def _ensure_canonical_subfolders(features_root: Path) -> tuple[str, ...]: 36 """Create missing canonical subfolders under features_root. 37 38 Args: 39 features_root: The features root directory. 40 41 Returns: 42 Tuple of subfolder names that were created. 43 """ 44 created: list[str] = [] 45 for name in _CANONICAL_SUBFOLDERS: 46 subfolder = features_root / name 47 if not subfolder.exists(): 48 subfolder.mkdir(parents=True, exist_ok=True) 49 created.append(name) 50 return tuple(created) 51 52 53def _migrate_loose_feature_files( 54 features_root: Path, 55) -> tuple[tuple[Path, ...], tuple[str, ...]]: 56 """Move any loose .feature files in features_root into backlog/. 57 58 Files that collide with an existing path in backlog/ are skipped 59 with a warning. 60 61 Args: 62 features_root: The features root directory. 63 64 Returns: 65 Tuple of (migrated_paths, warning_strings). 66 """ 67 migrated: list[Path] = [] 68 warnings: list[str] = [] 69 backlog_dir = features_root / "backlog" 70 for item in sorted(features_root.iterdir()): 71 if item.is_dir() or item.suffix != ".feature": 72 continue 73 target = backlog_dir / item.name 74 if target.exists(): 75 warnings.append(f"Cannot migrate {item}: {target} already exists") 76 continue 77 item.rename(target) 78 migrated.append(target) 79 return tuple(migrated), tuple(warnings) 80 81 82def bootstrap_features_directory(features_root: Path) -> BootstrapResult: 83 """Ensure the features directory has the canonical subfolder structure. 84 85 Creates backlog/, in-progress/, and completed/ if missing. Migrates 86 any loose .feature files at the root level into backlog/. 87 88 Args: 89 features_root: Root of the features directory. 90 91 Returns: 92 BootstrapResult describing what was done. 93 """ 94 if not features_root.exists(): 95 return BootstrapResult( 96 created_subfolders=(), 97 migrated_files=(), 98 collision_warnings=(), 99 ) 100 created = _ensure_canonical_subfolders(features_root) 101 migrated, warnings = _migrate_loose_feature_files(features_root) 102 return BootstrapResult( 103 created_subfolders=created, 104 migrated_files=migrated, 105 collision_warnings=warnings, 106 )
@dataclass(frozen=True, slots=True)
class
BootstrapResult:
12@dataclass(frozen=True, slots=True) 13class BootstrapResult: 14 """Result of bootstrapping the features directory. 15 16 Attributes: 17 created_subfolders: Names of subfolders that were created. 18 migrated_files: Paths of files that were migrated. 19 collision_warnings: Warning messages for name collisions. 20 """ 21 22 created_subfolders: tuple[str, ...] 23 migrated_files: tuple[Path, ...] 24 collision_warnings: tuple[str, ...] 25 26 @property 27 def is_noop(self) -> bool: 28 """Return True if bootstrap made no changes.""" 29 return ( 30 len(self.created_subfolders) == 0 31 and len(self.migrated_files) == 0 32 and len(self.collision_warnings) == 0 33 )
Result of bootstrapping the features directory.
Attributes: created_subfolders: Names of subfolders that were created. migrated_files: Paths of files that were migrated. collision_warnings: Warning messages for name collisions.
83def bootstrap_features_directory(features_root: Path) -> BootstrapResult: 84 """Ensure the features directory has the canonical subfolder structure. 85 86 Creates backlog/, in-progress/, and completed/ if missing. Migrates 87 any loose .feature files at the root level into backlog/. 88 89 Args: 90 features_root: Root of the features directory. 91 92 Returns: 93 BootstrapResult describing what was done. 94 """ 95 if not features_root.exists(): 96 return BootstrapResult( 97 created_subfolders=(), 98 migrated_files=(), 99 collision_warnings=(), 100 ) 101 created = _ensure_canonical_subfolders(features_root) 102 migrated, warnings = _migrate_loose_feature_files(features_root) 103 return BootstrapResult( 104 created_subfolders=created, 105 migrated_files=migrated, 106 collision_warnings=warnings, 107 )
Ensure the features directory has the canonical subfolder structure.
Creates backlog/, in-progress/, and completed/ if missing. Migrates any loose .feature files at the root level into backlog/.
Args: features_root: Root of the features directory.
Returns: BootstrapResult describing what was done.