143 lines
4.4 KiB
Python
143 lines
4.4 KiB
Python
#!/usr/bin/env python
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import zipfile
|
|
from collections.abc import Iterable
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
ROOT_NAME = "VacatureRadar_Project"
|
|
EXCLUDED_DIRS = {
|
|
".git",
|
|
".venv",
|
|
".pytest_cache",
|
|
".ruff_cache",
|
|
"__pycache__",
|
|
"backups",
|
|
"htmlcov",
|
|
"logs",
|
|
"media",
|
|
"staticfiles",
|
|
}
|
|
EXCLUDED_FILES = {".coverage", ".env", "celerybeat-schedule", "db.sqlite3"}
|
|
EXCLUDED_SUFFIXES = {".pyc", ".pyo"}
|
|
|
|
|
|
def is_included(path: Path) -> bool:
|
|
relative = path.relative_to(ROOT)
|
|
if any(part in EXCLUDED_DIRS for part in relative.parts):
|
|
return False
|
|
if path.name in EXCLUDED_FILES or path.suffix in EXCLUDED_SUFFIXES:
|
|
return False
|
|
if relative.parts and relative.parts[0] == "local" and path.name != ".gitkeep":
|
|
return False
|
|
return path.is_file()
|
|
|
|
|
|
def iter_files() -> Iterable[Path]:
|
|
files: list[Path] = []
|
|
for directory, dirnames, filenames in os.walk(ROOT):
|
|
dirnames[:] = sorted(name for name in dirnames if name not in EXCLUDED_DIRS)
|
|
base = Path(directory)
|
|
if base == ROOT / "local":
|
|
dirnames.clear()
|
|
for filename in sorted(filenames):
|
|
path = base / filename
|
|
if is_included(path):
|
|
files.append(path)
|
|
return sorted(files)
|
|
|
|
|
|
def sha256(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as handle:
|
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
digest.update(chunk)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def write_manifest(staging_root: Path, paths: Iterable[Path] | None = None) -> None:
|
|
entries = []
|
|
candidates = sorted(paths) if paths is not None else sorted(staging_root.rglob("*"))
|
|
for path in candidates:
|
|
if not path.is_file() or path.name == "PROJECT_MANIFEST.json":
|
|
continue
|
|
entries.append(
|
|
{
|
|
"path": path.relative_to(staging_root).as_posix(),
|
|
"bytes": path.stat().st_size,
|
|
"sha256": sha256(path),
|
|
}
|
|
)
|
|
manifest = {
|
|
"project": "VacatureRadar",
|
|
"artifact_root": ROOT_NAME,
|
|
"generated_at": datetime.now(UTC).isoformat(),
|
|
"file_count_excluding_manifest": len(entries),
|
|
"total_bytes_excluding_manifest": sum(item["bytes"] for item in entries),
|
|
"files": entries,
|
|
}
|
|
(staging_root / "PROJECT_MANIFEST.json").write_text(
|
|
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def package(output: Path) -> tuple[int, str]:
|
|
output = output.resolve()
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
if output.exists():
|
|
output.unlink()
|
|
with tempfile.TemporaryDirectory(prefix="vacatureradar-package-") as temporary:
|
|
staging_root = Path(temporary) / ROOT_NAME
|
|
staging_root.mkdir()
|
|
for source in iter_files():
|
|
relative = source.relative_to(ROOT)
|
|
destination = staging_root / relative
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(source, destination)
|
|
write_manifest(staging_root)
|
|
with zipfile.ZipFile(
|
|
output, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
|
|
) as archive:
|
|
for path in sorted(staging_root.rglob("*")):
|
|
if path.is_file():
|
|
archive.write(path, path.relative_to(staging_root.parent))
|
|
return output.stat().st_size, sha256(output)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Maak een schone VacatureRadar-project-ZIP")
|
|
parser.add_argument(
|
|
"--manifest-only",
|
|
action="store_true",
|
|
help="Werk alleen PROJECT_MANIFEST.json in de repository bij.",
|
|
)
|
|
parser.add_argument(
|
|
"output",
|
|
nargs="?",
|
|
type=Path,
|
|
default=ROOT.parent / "VacatureRadar_Project.zip",
|
|
)
|
|
args = parser.parse_args()
|
|
if args.manifest_only:
|
|
write_manifest(ROOT, iter_files())
|
|
print(f"Manifest: {(ROOT / 'PROJECT_MANIFEST.json').resolve()}")
|
|
return 0
|
|
size, checksum = package(args.output)
|
|
print(f"ZIP: {args.output.resolve()}")
|
|
print(f"Bytes: {size}")
|
|
print(f"SHA256: {checksum}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|