"""Block committed image/video files and asset-style directories.""" from __future__ import annotations import subprocess from collections.abc import Iterable from dataclasses import dataclass from pathlib import PurePosixPath BLOCKED_DIR_NAMES = frozenset( { "asset", "assets", "image", "images", "img", "media", "video", "videos", } ) IMAGE_EXTENSIONS = frozenset( { ".avif", ".bmp", ".gif", ".heic", ".heif", ".icns", ".ico", ".jpeg", ".jpg", ".png", ".svg", ".tif", ".tiff", ".webp", } ) VIDEO_EXTENSIONS = frozenset( { ".avi", ".flv", ".m4v", ".mkv", ".mov", ".mp4", ".mpeg", ".mpg", ".webm", ".wmv", } ) @dataclass(frozen=True) class Violation: path: str reason: str def _normalise_path(path: str) -> PurePosixPath: return PurePosixPath(path.replace("\\", "/")) def _violation_reason(path: str) -> str | None: posix_path = _normalise_path(path) lower_parts = tuple(part.lower() for part in posix_path.parts) if any(part in BLOCKED_DIR_NAMES for part in lower_parts): return "asset/media directory" suffix = posix_path.suffix.lower() if suffix in IMAGE_EXTENSIONS: return "image file" if suffix in VIDEO_EXTENSIONS: return "video file" return None def find_violations(paths: Iterable[str]) -> list[Violation]: violations: list[Violation] = [] for path in paths: reason = _violation_reason(path) if reason is not None: violations.append(Violation(path=path, reason=reason)) return violations def _tracked_paths() -> list[str]: result = subprocess.run( ["git", "ls-files", "-z"], check=True, stdout=subprocess.PIPE, text=False, ) return [raw.decode("utf-8") for raw in result.stdout.split(b"\0") if raw] def main() -> int: violations = find_violations(_tracked_paths()) if not violations: print("Repository asset/media check passed.") return 0 print( "Repository asset/media check failed.\n" "Do not commit images, videos, or asset/media directories. " "Host visual media externally, in release artifacts, or another " "approved storage location, then link to it from docs.\n" ) for violation in violations: print(f"- {violation.path}: {violation.reason}") return 1 if __name__ == "__main__": raise SystemExit(main())