diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 65433e9..793d7e2 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -124,25 +124,7 @@ jobs: set -eu tag=$(date -u '+%Y.%m.%d-%H%M') echo "tag=$tag" >> "$GITHUB_OUTPUT" - { - echo "Собрано из \`$(git rev-parse --short HEAD)\`." - echo - echo '| Категория | Доменов |' - echo '| --- | ---: |' - python3 -c "import json; [print(f'| {k} | {v} |') for k, v in json.load(open('dist/metadata.json'))['categories'].items()]" - echo - echo '| IP-категория | Сетей | Ограничения |' - echo '| --- | ---: | --- |' - python3 -c " -import json -for k, v in json.load(open('dist/metadata.json'))['ip_categories'].items(): - limits = ' '.join(filter(None, ['/'.join(v.get('network', [])), v.get('ports', '')])) or '—' - print(f\"| {k} | {v['count']} | {limits} |\") -" - echo - echo "sha256(discord-geosite.dat) = \`$(cut -d' ' -f1 dist/discord-geosite.dat.sha256sum)\`" - echo "sha256(discord-geoip.dat) = \`$(cut -d' ' -f1 dist/discord-geoip.dat.sha256sum)\`" - } > /tmp/notes.md + python3 tools/release_notes.py "$(git rev-parse --short HEAD)" > /tmp/notes.md cat /tmp/notes.md - name: Публикация в ветку release diff --git a/tools/release_notes.py b/tools/release_notes.py new file mode 100644 index 0000000..72de249 --- /dev/null +++ b/tools/release_notes.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Печатает описание релиза в markdown по dist/metadata.json. + + python3 tools/release_notes.py <короткий-sha> [каталог-сборки] + +Отдельным скриптом, а не строчкой в CI: внутри YAML такой markdown +пришлось бы экранировать до нечитаемости. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + + +def render(meta: dict, revision: str) -> str: + lines = [f"Собрано из `{revision}`.", ""] + + lines += ["| Категория | Доменов |", "| --- | ---: |"] + for name, count in meta["categories"].items(): + lines.append(f"| {name} | {count} |") + lines.append("") + + lines += ["| IP-категория | Сетей | Ограничения |", "| --- | ---: | --- |"] + for name, info in meta["ip_categories"].items(): + limits = " ".join(filter(None, ["/".join(info.get("network", [])), info.get("ports", "")])) + lines.append(f"| {name} | {info['count']} | {limits or '—'} |") + lines.append("") + + lines += [ + f"sha256(discord-geosite.dat) = `{meta['geosite_dat_sha256']}`", + "", + f"sha256(discord-geoip.dat) = `{meta['geoip_dat_sha256']}`", + ] + return "\n".join(lines) + "\n" + + +def main(argv: list[str]) -> int: + sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr] + if not 2 <= len(argv) <= 3: + print(f"использование: {argv[0]} <короткий-sha> [каталог-сборки]", file=sys.stderr) + return 1 + + out_dir = Path(argv[2]) if len(argv) == 3 else Path("dist") + meta = json.loads((out_dir / "metadata.json").read_text(encoding="utf-8")) + sys.stdout.write(render(meta, argv[1])) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/tools/test_build.py b/tools/test_build.py index a727dbb..f88871c 100644 --- a/tools/test_build.py +++ b/tools/test_build.py @@ -11,6 +11,7 @@ import unittest from pathlib import Path import build +import release_notes import sync from build import BuildError, Net, Options, Rule @@ -336,6 +337,34 @@ class SyncTest(unittest.TestCase): self.assertTrue(sync.is_generated(Path(tmp) / "new.txt")) +class ReleaseNotesTest(unittest.TestCase): + META = { + "geosite_dat_sha256": "aa", + "geoip_dat_sha256": "bb", + "categories": {"discord": 34}, + "ip_categories": { + "discord": {"count": 140}, + "discord-voice": {"count": 13, "network": ["udp"], "ports": "50000-50099"}, + }, + } + + def test_renders_both_tables(self): + notes = release_notes.render(self.META, "abc1234") + self.assertIn("Собрано из `abc1234`.", notes) + self.assertIn("| discord | 34 |", notes) + self.assertIn("| discord | 140 | — |", notes) + self.assertIn("| discord-voice | 13 | udp 50000-50099 |", notes) + self.assertIn("sha256(discord-geoip.dat) = `bb`", notes) + + def test_reads_real_metadata(self): + with tempfile.TemporaryDirectory() as out: + out_dir = Path(out) / "dist" + with data(a="a.com\n", ip={"n": "1.2.3.0/24\n"}): + build.build(out_dir, None, None, "proxy") + meta = json.loads((out_dir / "metadata.json").read_text(encoding="utf-8")) + self.assertIn("| n | 1 | — |", release_notes.render(meta, "abc1234")) + + class CheckTest(unittest.TestCase): def test_clean_sources_pass(self): with data(a="a.com\nb.com\n", ip={"a": "1.2.3.0/24\n"}):