#!/usr/bin/env python3 """Build the SkillHub weekly report site from self-contained report files.""" from __future__ import annotations import argparse import json import shutil from html import escape from pathlib import Path def load_manifest(source: Path) -> tuple[str, list[dict[str, str]]]: manifest_path = source / "reports.json" payload = json.loads(manifest_path.read_text(encoding="utf-8")) latest = payload.get("latest") reports = payload.get("reports") if not isinstance(latest, str) or not latest: raise ValueError("reports.json must define a non-empty latest week") if not isinstance(reports, list) or not reports: raise ValueError("reports.json must contain at least one report") required = {"week", "title", "period", "snapshot", "path"} normalized: list[dict[str, str]] = [] for index, report in enumerate(reports): if not isinstance(report, dict) or not required.issubset(report): missing = required - set(report) if isinstance(report, dict) else required raise ValueError(f"report #{index + 1} is missing fields: {sorted(missing)}") normalized.append({key: str(report[key]) for key in required}) weeks = {report["week"] for report in normalized} if latest not in weeks: raise ValueError(f"latest week {latest!r} is not present in reports") return latest, sorted(normalized, key=lambda item: item["week"], reverse=True) def render_archive(latest: str, reports: list[dict[str, str]]) -> str: rows = "\n".join( f"""
  • {escape(report['week'])} {'最新' if report['week'] == latest else ''} {escape(report['title'])} {escape(report['period'])} · 快照 {escape(report['snapshot'])}
  • """ for report in reports ) return f""" SkillHub 开源周报归档
    SkillHub 开源周报

    SkillHub 开源周报归档

    按统计周期倒序查看历期开源周报。

    当前共收录 {len(reports)} 期,最新一期为 {escape(latest)}
    返回最新周报
    """ def build(source: Path, output: Path) -> None: latest, reports = load_manifest(source) latest_report = next(report for report in reports if report["week"] == latest) latest_source = source / latest_report["path"] / "index.html" if not latest_source.is_file(): raise FileNotFoundError(f"latest report not found: {latest_source}") for report in reports: report_file = source / report["path"] / "index.html" if not report_file.is_file(): raise FileNotFoundError(f"report not found: {report_file}") if output.exists(): shutil.rmtree(output) shutil.copytree(source, output) latest_html = latest_source.read_text(encoding="utf-8") latest_html = latest_html.replace('href="../../archive.html"', 'href="./archive.html"') (output / "index.html").write_text(latest_html, encoding="utf-8") (output / "archive.html").write_text( render_archive(latest, reports), encoding="utf-8", ) (output / ".nojekyll").write_text("", encoding="utf-8") print(f"Built {len(reports)} report(s); latest={latest}; output={output}") def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--source", type=Path, default=Path("site")) parser.add_argument("--output", type=Path, default=Path("_site")) args = parser.parse_args() build(args.source.resolve(), args.output.resolve()) if __name__ == "__main__": main()