"""A platform page lists one row per file, the evidence on demand. Batocera, AmberELEC and RomM ran to 14,000 to 16,000 words: every file spelled its full MD5, the copy serving it and each source reference of each reader, with "(emulator code, libretro port)" after every one. A row now shows the name, the folder, the status and a short hash. Opened, it holds every hash, the served copy and the readers across the list, the role of each reference named by one word that a legend defines once. """ from __future__ import annotations import html import re import sys import unittest from html.parser import HTMLParser from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT / "scripts")) import generate_site # noqa: E402 _SKIPPED = frozenset({"script", "style", "template"}) _VOID = frozenset({"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"}) class _Words(HTMLParser): """Words in
: all of them, and those a reader sees with every details block closed (a closed block shows its summary only).""" def __init__(self) -> None: super().__init__(convert_charrefs=True) self.stack: list[tuple[str, bool]] = [] self.visible = 0 self.total = 0 def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: if tag not in _VOID: closed = tag == "details" and "open" not in dict(attrs) self.stack.append((tag, closed)) def handle_endtag(self, tag: str) -> None: for index in range(len(self.stack) - 1, -1, -1): if self.stack[index][0] == tag: del self.stack[index:] return def _counted(self) -> bool: names = {name for name, _ in self.stack} return "main" in names and not names & _SKIPPED def _shown(self) -> bool: for index, (_name, closed) in enumerate(self.stack): if closed: rest = self.stack[index + 1:] if not rest or rest[0][0] != "summary": return False return True def handle_data(self, data: str) -> None: if not self._counted(): return count = len(data.split()) self.total += count if self._shown(): self.visible += count def page_words(html: str) -> tuple[int, int]: """(visible words, all words) in a page's
.""" parser = _Words() parser.feed(html) return parser.visible, parser.total class VisibleWordsAreCounted(unittest.TestCase): def test_a_closed_block_shows_its_summary_only(self): page = ("

one two

threefour five" "
sixseven
" "
eightnine
" "

outside

") self.assertEqual(page_words(page), (5, 9)) class ARowPerFile(unittest.TestCase): def _row(self, f: dict, entry: dict, hash_type: str = "md5") -> str: return generate_site._platform_file_row(f, entry, hash_type) def test_the_row_at_a_glance(self): md5, sha1 = "a" * 32, "b" * 40 row = self._row({"name": "bios_a.bin", "status": "ok", "size": 2048}, {"name": "bios_a.bin", "md5": md5, "sha1": sha1, "crc32": "0badf00d", "destination": "bios/bios_a.bin"}) # The summary is the row: the name, its folder, the status. self.assertIn('
bios_a.binbios/' 'OK', row) # The hash the platform checks, short, on one button beside the # summary (a summary is a control: no button inside it). details, button = row.split("
") self.assertNotIn("{md5[:8]}') # Every hash whole in the details, with the size. for value in (md5, sha1, "0badf00d"): self.assertIn(value, details) self.assertIn("(2.0 KB)", details) def test_a_file_at_the_root_prints_no_path(self): for destination in ("", "x.bin"): row = self._row({"name": "x.bin", "status": "ok"}, {"destination": destination}) self.assertIn("x.bin', text) config[(system, "b.bin")] = [{"destination": "sub/b.bin"}] text = "\n".join(generate_site._render_platform_systems({system: files}, config, {})) self.assertIn('
', text) def test_status_words(self): cases = { ("missing", ""): ("Missing", "danger"), ("untested", "declared hash contradicted by the local file"): ("Wrong hash", "warning"), ("untested", "expected 1234... got 5678..."): ("Wrong hash", "warning"), ("untested", "bios/x read error"): ("Untested", "warning"), } for (status, reason), (word, tone) in cases.items(): row = self._row({"name": "x", "status": status, "reason": reason}, {"md5": "c" * 32}) self.assertIn(f'{word}', row) if reason: self.assertIn(f"Not confirmed: {reason}", row) def test_accepted_values_all_in_the_details(self): values = ["1" * 32, "2" * 32, "3" * 32] row = self._row({"name": "x", "status": "ok"}, {"md5": ",".join(values)}) self.assertIn(f'data-copy="{values[0]}"', row) self.assertIn(f">{values[0][:8]} +2", row) for value in values: self.assertIn(f'{value}', row) def test_markdown_never_reads_the_text(self): row = self._row({"name": "*a_b*[1].bin", "status": "ok"}, {}) self.assertIn("*a_b*[1].bin", row) def test_a_long_list_gets_a_filter(self): rows = ["
"] * generate_site.FILTER_ROWS block = generate_site._platform_file_list(rows, False, "arcade") self.assertTrue(block[0].startswith('