diff --git a/scripts/exporter/retrobat_exporter.py b/scripts/exporter/retrobat_exporter.py index 22ce255c..c14d064f 100644 --- a/scripts/exporter/retrobat_exporter.py +++ b/scripts/exporter/retrobat_exporter.py @@ -7,6 +7,7 @@ state md5 then file, in that order. from __future__ import annotations import json +import re from collections import OrderedDict from .base_exporter import BaseExporter @@ -18,6 +19,33 @@ SOURCE_URL = ( ) +_TOP_KEY = re.compile(r'^ "((?:[^"\\]|\\.)*)":', re.MULTILINE) + + +def keep_blank_lines(original: str, produced: str) -> str: + """The blank line RetroBat writes before each system, written again. + + json.dumps has no blank lines, and the original separates every system + after the first with one: dropped, they were 100 hunks of a diff meant + to show corrections. A system the original separates is separated, and + where it separates them all, an added one is too. + """ + keys = _TOP_KEY.findall(original) + separated = set(re.findall(r'\n\n "((?:[^"\\]|\\.)*)":', original)) + every = bool(keys) and separated == set(keys[1:]) + first = True + + def separate(match: re.Match[str]) -> str: + nonlocal first + key, was_first = match.group(1), first + first = False + if not was_first and (key in separated or (every and key not in keys)): + return "\n" + match.group(0) + return match.group(0) + + return _TOP_KEY.sub(separate, produced) + + class Exporter(BaseExporter): """Write RetroBat's batocera-systems.json, corrected.""" @@ -105,7 +133,8 @@ class Exporter(BaseExporter): output[key] = system_entry text = json.dumps(output, indent=2, ensure_ascii=False) + "\n" - return {self.native_filename(): text} + original = (originals or {}).get(self.native_filename(), "") + return {self.native_filename(): keep_blank_lines(original, text) if original else text} def validate( self, diff --git a/tests/test_exporters.py b/tests/test_exporters.py index 67351c8f..840898f1 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -1420,6 +1420,31 @@ class BizhawkExport(unittest.TestCase): self.assertFalse(exporter.states(entry, "sha1")) +class RetrobatListComesBackAsWritten(unittest.TestCase): + """RetroBat separates each system with a blank line. json.dumps dropped + all 100 of them, 100 hunks of noise in a diff meant to show + corrections.""" + + def test_an_empty_truth_hands_back_the_list(self): + original_path = UPSTREAM_CACHE / "retrobat" / "batocera-systems.json" + if not original_path.exists(): + self.skipTest("upstream cache not populated") + original = original_path.read_text(encoding="utf-8") + exporter = discover_exporters()["retrobat"]() + config = native_layer(load_platform_config("retrobat", str(REPO_ROOT / "platforms"))) + systems, report = build_native_model({"systems": {}}, config) + produced = exporter.render(systems, report, {"batocera-systems.json": original}, config) + self.assertEqual(produced["batocera-systems.json"], original) + + def test_an_added_system_takes_the_same_separator(self): + from exporter.retrobat_exporter import keep_blank_lines # noqa: PLC0415 + + original = '{\n "a": {},\n\n "b": {}\n}\n' + produced = '{\n "a": {},\n "b": {},\n "c": {}\n}\n' + self.assertEqual(keep_blank_lines(original, produced), + '{\n "a": {},\n\n "b": {},\n\n "c": {}\n}\n') + + class MisterDatabaseComesBackAsWritten(unittest.TestCase): """The BiosDB is one line of JSON. Written with indent=2 it came out as 1499 changed lines for a database whose content had not changed."""