import argparse
import hashlib
import html
import os
import re
import string
import time
from pathlib import Path
from dist import ROOT, cargo_meta
TEMPLATE = ROOT / "packaging" / "index.html.in"
ARCH_LABEL = {"x86_64": "x86_64", "amd64": "x86_64", "aarch64": "aarch64 (Pi 4/5)", "arm64": "aarch64 (Pi 4/5)"}
KIND_LABEL = {"deb": "Debian package", "tar.gz": "tar.gz"}
NAME = re.compile(r"medias[-_](\d+(?:\.\d+)*)(?:-\d+)?[-_](?:linux-)?(x86_64|aarch64|amd64|arm64)\.(tar\.gz|deb)$")
def version_key(version):
return tuple(int(part) for part in version.split("."))
def collect(out):
found = {}
pool = sorted((out / "debian" / "pool").rglob("*.deb")) if (out / "debian" / "pool").is_dir() else []
for path in sorted(out.glob("medias*")) + pool:
match = NAME.match(path.name)
if not match:
continue
version, arch, kind = match.groups()
entries = found.setdefault(version, {})
key = (ARCH_LABEL[arch], kind)
if key not in entries:
entries[key] = (path.relative_to(out).as_posix(), path.stat().st_size, sha256(path))
return {
version: [(arch, kind, *entry) for (arch, kind), entry in sorted(entries.items(), key=lambda e: (e[0][0], e[0][1] != "deb"))]
for version, entries in found.items()
}
def sha256(path):
side = Path(f"{path}.sha256")
if side.exists():
return side.read_text().split()[0]
return hashlib.sha256(path.read_bytes()).hexdigest()
def table(rows, indent=" "):
lines = ["<table>", " <thead><tr><th>Architecture</th><th>File</th><th>Size</th><th>SHA-256</th></tr></thead>", " <tbody>"]
for arch, kind, href, size, digest in rows:
name = html.escape(href.rsplit("/", 1)[-1])
lines.append(
f" <tr><td>{html.escape(arch)}</td>"
f'<td><a href="{html.escape(href, quote=True)}">{name}</a> <span class="note">{KIND_LABEL[kind]}</span></td>'
f'<td class="num">{size / 1048576:.1f} MiB</td><td class="hash">{digest}</td></tr>'
)
lines += [" </tbody>", "</table>"]
return "\n".join(indent + line for line in lines)
def render(out, version, description, repo_url, now):
versions = collect(out)
latest = versions.pop(version, None)
if latest is None and versions:
version = max(versions, key=version_key)
latest = versions.pop(version)
older = ""
if versions:
parts = ["<details>", " <summary>Older versions</summary>"]
for old in sorted(versions, key=version_key, reverse=True):
parts += [f" <h2>{html.escape(old)}</h2>", ' <div class="panel">', table(versions[old]), " </div>"]
parts.append("</details>")
older = "\n".join(" " + line for line in parts)
short, _, long = description.partition(": ")
text = f"{short}: {long}." if long else short
return string.Template(TEMPLATE.read_text()).substitute(
version=html.escape(version),
description=html.escape(text, quote=True),
sources_url=html.escape(f"{repo_url.rstrip('/')}/medias.sources"),
latest=table(latest or []),
older=older,
date=time.strftime("%Y-%m-%d", time.gmtime(now)),
)
def write(out=ROOT / "dist", repo_url=None):
_, version, description = cargo_meta()
repo_url = repo_url or os.environ.get("MEDIAS_REPO_URL") or "debian"
path = out / "index.html"
path.write_text(render(out, version, description, repo_url, time.time()))
return path
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--out", type=Path, default=ROOT / "dist", help="directory with the downloads")
parser.add_argument("--url", help="public URL of dist/debian (default: $MEDIAS_REPO_URL)")
args = parser.parse_args()
print(f"wrote {write(args.out, args.url)}")