diff --git a/scripts/exporter/systemdat_exporter.py b/scripts/exporter/systemdat_exporter.py index 5ceda9e1..fdae2e3c 100644 --- a/scripts/exporter/systemdat_exporter.py +++ b/scripts/exporter/systemdat_exporter.py @@ -36,7 +36,10 @@ def _quote(name: str, quoted: frozenset[str] = frozenset()) -> str: return f'"{name}"' if name in quoted or any(c in name for c in ' ()') else name -_ROM_NAME = re.compile(r'^\trom \( name ("[^"]+"|\S+)') +# libretro indents most lines with a tab and 39 with four spaces (the Amiga +# kickstarts, the TI-83). Read with a tab only, those lines left the base and +# came back re-rendered, re-indented and moved to the end of their section. +_ROM_NAME = re.compile(r'^(\s+)rom \( name ("[^"]+"|\S+)') def _sections(original: str) -> list[tuple[str, list[str]]]: @@ -46,9 +49,10 @@ def _sections(original: str) -> list[tuple[str, list[str]]]: return [] sections: list[tuple[str, list[str]]] = [] for line in body.splitlines(): - if line.startswith('\tcomment "') and line != '\tcomment "System"': - sections.append((line[len('\tcomment "'):].rstrip('"'), [])) - elif line.startswith("\trom (") and sections: + stripped = line.lstrip() + if stripped.startswith('comment "') and stripped != 'comment "System"': + sections.append((stripped[len('comment "'):].rstrip('"'), [])) + elif stripped.startswith("rom (") and line != stripped and sections: sections[-1][1].append(line) return sections @@ -180,7 +184,11 @@ class Exporter(BaseExporter): body = [] for line in roms: match = _ROM_NAME.match(line) - body.append(rendered.pop(match.group(1), line) if match else line) + if match and match.group(2) in rendered: + # The values are ours, the indentation stays libretro's. + body.append(match.group(1) + rendered.pop(match.group(2)).lstrip("\t")) + else: + body.append(line) body.extend(rendered.values()) section(comment, body) for comment in sorted(ours): diff --git a/tests/test_exporters.py b/tests/test_exporters.py index d2e048a9..a19ab1ac 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -782,6 +782,37 @@ class RommExport(unittest.TestCase): self.assertTrue(all(k.startswith("psx:") for k in data)) +class SystemDatKeepsSpaceIndentedLines(unittest.TestCase): + """libretro indents 39 rom lines with four spaces. Read with a tab only, + they left the base and came back last in their section, re-indented.""" + + def test_order_and_indentation_survive(self): + amiga = "Commodore - Amiga" + a, b = "1" * 32, "2" * 32 + original = ( + 'clrmamepro (\n\tname "System"\n)\n\ngame (\n\tname "System"\n' + '\tcomment "System"\n\n' + f'\tcomment "{amiga}"\n' + f" rom ( name kick33180.A500 size 262144 md5 {a} )\n" + f"\trom ( name kick34005.A500 size 262144 md5 {b} )\n" + ")\n" + ) + scraped = {"systems": {"commodore-amiga": {"native_id": amiga, "files": [ + {"name": "kick33180.A500", "destination": "kick33180.A500", + "native_system": amiga, "md5": a, "size": 262144}, + {"name": "kick34005.A500", "destination": "kick34005.A500", + "native_system": amiga, "md5": b, "size": 262144}, + ]}}} + systems, report = build_native_model({"systems": {}}, scraped) + produced = discover_exporters()["retroarch"]().render( + systems, report, {"System.dat": original}, None + )["System.dat"] + body = produced[produced.index(f'comment "{amiga}"'):].splitlines()[1:3] + self.assertTrue(body[0].startswith(" rom ( name kick33180.A500"), body) + self.assertTrue(body[1].startswith("\trom ( name kick34005.A500"), body) + self.assertEqual(produced.count("kick33180.A500"), 1) + + class RommSizeIsNeverNone(unittest.TestCase): """size() is None where the truth contradicts RomM's hash without a size or names several sizes; str(None) went out as "None", validate() passed