84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
"""Construye un ZIP reproducible y limpio del Bot Digitador."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import sys
|
|
from pathlib import Path
|
|
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from bot_digitador.core.config import REQUIRED_TEMPLATE_NAMES # noqa: E402
|
|
|
|
|
|
DEFAULT_OUTPUT = ROOT.parent / "bot_digitador.zip"
|
|
TOP_LEVEL_FILES = (".env.example", "README.md", "requirements.txt", "run_gui.py", "main.py")
|
|
EXCLUDED_PARTS = {"__pycache__", ".pytest_cache", ".venv", "data", "reportes_bot", "out"}
|
|
EXCLUDED_SUFFIXES = {".pyc", ".pyo", ".log"}
|
|
|
|
|
|
def package_files(root: Path) -> list[Path]:
|
|
files: list[Path] = []
|
|
for name in TOP_LEVEL_FILES:
|
|
path = root / name
|
|
if path.is_file():
|
|
files.append(path)
|
|
for folder in (root / "bot_digitador", root / "template"):
|
|
if not folder.exists():
|
|
continue
|
|
for path in folder.rglob("*"):
|
|
relative = path.relative_to(root)
|
|
if not path.is_file() or any(part in EXCLUDED_PARTS for part in relative.parts):
|
|
continue
|
|
if path.suffix.lower() in EXCLUDED_SUFFIXES or path.name == ".env":
|
|
continue
|
|
files.append(path)
|
|
return sorted(files, key=lambda path: path.relative_to(root).as_posix())
|
|
|
|
|
|
def validate_assets(root: Path) -> None:
|
|
missing = [name for name in REQUIRED_TEMPLATE_NAMES if not (root / "template" / name).is_file()]
|
|
if missing:
|
|
raise RuntimeError(
|
|
"No se puede crear un paquete ejecutable. Faltan plantillas: " + ", ".join(missing)
|
|
)
|
|
|
|
|
|
def build_package(root: Path = ROOT, output: Path = DEFAULT_OUTPUT, *, require_templates: bool = True) -> Path:
|
|
if require_templates:
|
|
validate_assets(root)
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
with ZipFile(output, "w", compression=ZIP_DEFLATED, compresslevel=9) as archive:
|
|
for source in package_files(root):
|
|
relative = source.relative_to(root).as_posix()
|
|
info = ZipInfo(f"bot_digitador/{relative}", date_time=(2020, 1, 1, 0, 0, 0))
|
|
info.compress_type = ZIP_DEFLATED
|
|
info.external_attr = 0o644 << 16
|
|
archive.writestr(info, source.read_bytes())
|
|
return output
|
|
|
|
|
|
def sha256(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as source:
|
|
for chunk in iter(lambda: source.read(1024 * 1024), b""):
|
|
digest.update(chunk)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
|
|
parser.add_argument("--allow-missing-templates", action="store_true")
|
|
args = parser.parse_args()
|
|
target = build_package(output=args.output.resolve(), require_templates=not args.allow_missing_templates)
|
|
print(f"Paquete: {target}")
|
|
print(f"SHA256: {sha256(target)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|