Represents and validates a preset manifest (preset.yml).
| 119 | |
| 120 | |
| 121 | class PresetManifest: |
| 122 | """Represents and validates a preset manifest (preset.yml).""" |
| 123 | |
| 124 | SCHEMA_VERSION = "1.0" |
| 125 | REQUIRED_FIELDS = ["schema_version", "preset", "requires", "provides"] |
| 126 | |
| 127 | def __init__(self, manifest_path: Path): |
| 128 | """Load and validate preset manifest. |
| 129 | |
| 130 | Args: |
| 131 | manifest_path: Path to preset.yml file |
| 132 | |
| 133 | Raises: |
| 134 | PresetValidationError: If manifest is invalid |
| 135 | """ |
| 136 | self.path = manifest_path |
| 137 | self.data = self._load_yaml(manifest_path) |
| 138 | self._validate() |
| 139 | |
| 140 | def _load_yaml(self, path: Path) -> dict: |
| 141 | """Load YAML file safely.""" |
| 142 | try: |
| 143 | with open(path, 'r', encoding='utf-8') as f: |
| 144 | data = yaml.safe_load(f) |
| 145 | except yaml.YAMLError as e: |
| 146 | raise PresetValidationError(f"Invalid YAML in {path}: {e}") |
| 147 | except FileNotFoundError: |
| 148 | raise PresetValidationError(f"Manifest not found: {path}") |
| 149 | except UnicodeDecodeError as e: |
| 150 | raise PresetValidationError( |
| 151 | f"Manifest is not valid UTF-8: {path} ({e.reason} at byte {e.start})" |
| 152 | ) |
| 153 | except OSError as e: |
| 154 | raise PresetValidationError(f"Could not read manifest {path}: {e}") |
| 155 | if data is None: |
| 156 | return {} |
| 157 | if not isinstance(data, dict): |
| 158 | raise PresetValidationError( |
| 159 | f"Manifest must be a YAML mapping, got {type(data).__name__}: {path}" |
| 160 | ) |
| 161 | return data |
| 162 | |
| 163 | def _validate(self): |
| 164 | """Validate manifest structure and required fields.""" |
| 165 | # Check required top-level fields |
| 166 | for field in self.REQUIRED_FIELDS: |
| 167 | if field not in self.data: |
| 168 | raise PresetValidationError(f"Missing required field: {field}") |
| 169 | |
| 170 | # Validate schema version |
| 171 | if self.data["schema_version"] != self.SCHEMA_VERSION: |
| 172 | raise PresetValidationError( |
| 173 | f"Unsupported schema version: {self.data['schema_version']} " |
| 174 | f"(expected {self.SCHEMA_VERSION})" |
| 175 | ) |
| 176 | |
| 177 | # Validate preset metadata |
| 178 | pack = self.data["preset"] |
no outgoing calls