@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
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]:
|
||||
return sorted(path for path in ROOT.rglob("*") if is_included(path))
|
||||
|
||||
|
||||
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) -> None:
|
||||
entries = []
|
||||
for path in sorted(staging_root.rglob("*")):
|
||||
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(
|
||||
"output",
|
||||
nargs="?",
|
||||
type=Path,
|
||||
default=ROOT.parent / "VacatureRadar_Project.zip",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
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())
|
||||
Reference in New Issue
Block a user