mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-23 20:29:06 -05:00
Aliases equal to a name a platform launcher chooses were read in each emulator's code at its pin. Removed where the code opens no such name: xemu Complex_4627v1.03.bin (the core offers Complex_4627_v1.03.bin), mednafen gecard.pce, dosbox bios.rom, raze Roland_SC-55.sf2, nine DraStic names, and clk's upper-case MSX names, whose paths now read msx.rom, msx2.rom and msx2ext.rom as ROMCatalogue.cpp:777-841 names them. Kept aliases now name the code list they come from in the entry note.
154 lines
5.9 KiB
Python
154 lines
5.9 KiB
Python
"""A profile alias is a name the emulator's own code opens.
|
|
|
|
Platform launchers pick names of their own: Batocera passes cerbios.bin to
|
|
xemu, ROCKNIX and EmuDeck write Complex_4627v1.03.bin into xemu.toml. Those
|
|
names live in the platform's ``launchers:`` layer. Copied into a profile as
|
|
aliases, they claimed that xemu opens them, and xemu-libretro offers
|
|
Complex_4627_v1.03.bin, not Complex_4627v1.03.bin (ui/xemu-libretro.c:115-118).
|
|
|
|
Two checks read the committed data:
|
|
|
|
- an alias equal to a name a launcher of the same emulator chooses is kept
|
|
only when the entry's own note or description names it in a sentence that
|
|
speaks of no platform, next to a ``source_ref`` into the emulator's code;
|
|
- no entry cites a platform's launcher script as its source.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import unittest
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
PLATFORMS = REPO_ROOT / "platforms"
|
|
EMULATORS = REPO_ROOT / "emulators"
|
|
|
|
# Words that name a platform in prose. MiSTer's registry key carries its
|
|
# hardware suffix, the prose says MiSTer.
|
|
PLATFORM_WORDS = (
|
|
"retroarch", "batocera", "recalbox", "retrobat", "emudeck", "lakka",
|
|
"retrodeck", "romm", "rocknix", "retropie", "bizhawk", "mister",
|
|
)
|
|
|
|
# Launcher sources: configgen and emulatorLauncher generators, EmuDeck and
|
|
# RetroPie scripts, a ref annotated as a launcher.
|
|
LAUNCHER_REF = re.compile(
|
|
r"Generator\.(?:py|cs)\b|\bconfiggen\b|\bemulatorLauncher\b|"
|
|
r"\bemuDeck\w*\.sh\b|\bscriptmodules/|\(launcher\)",
|
|
)
|
|
|
|
# Profiles whose entries still rest on a launcher, with the fact that blocks
|
|
# reading the emulator itself.
|
|
LAUNCHER_SOURCED = {
|
|
"lexaloffle": "PICO-8 and Voxatron are sold binaries, and no copy of either is held to read",
|
|
}
|
|
|
|
_SENTENCE = re.compile(r"(?<=[.!?])\s+(?=[A-Z0-9(\"'])")
|
|
|
|
|
|
def _profiles() -> dict[str, dict]:
|
|
return {
|
|
path.stem: yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
for path in sorted(EMULATORS.glob("*.yml"))
|
|
}
|
|
|
|
|
|
def _launcher_names() -> dict[str, set[tuple[str, str]]]:
|
|
"""Folded file name -> (platform, emulator) of each launcher choosing it."""
|
|
names: dict[str, set[tuple[str, str]]] = defaultdict(set)
|
|
for path in sorted(PLATFORMS.glob("*.yml")):
|
|
config = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
for record in config.get("launchers") or []:
|
|
for name in record.get("names") or []:
|
|
base = str(name).rsplit("/", 1)[-1].casefold()
|
|
names[base].add((path.stem, str(record.get("emulator", ""))))
|
|
return names
|
|
|
|
|
|
def _text(value: object) -> str:
|
|
if isinstance(value, dict):
|
|
return " ".join(_text(v) for v in value.values())
|
|
if isinstance(value, list):
|
|
return " ".join(_text(v) for v in value)
|
|
return str(value or "")
|
|
|
|
|
|
def _named_without_platform(alias: str, prose: str) -> bool:
|
|
for sentence in _SENTENCE.split(" ".join(prose.split())):
|
|
if alias not in sentence:
|
|
continue
|
|
folded = sentence.casefold()
|
|
if not any(re.search(rf"\b{word}", folded) for word in PLATFORM_WORDS):
|
|
return True
|
|
return False
|
|
|
|
|
|
class ProfileAliasesAreCodeNames(unittest.TestCase):
|
|
def test_alias_a_launcher_chooses_is_named_by_the_code(self) -> None:
|
|
launchers = _launcher_names()
|
|
offenders = []
|
|
for key, profile in _profiles().items():
|
|
emulator_names = {key} | {str(c) for c in profile.get("cores") or []}
|
|
for entry in profile.get("files") or []:
|
|
prose = _text(entry.get("note")) + " " + _text(entry.get("description"))
|
|
for alias in entry.get("aliases") or []:
|
|
alias = str(alias)
|
|
chosen_by = {
|
|
platform
|
|
for platform, emulator in launchers.get(
|
|
alias.rsplit("/", 1)[-1].casefold(), ()
|
|
)
|
|
if emulator in emulator_names
|
|
}
|
|
if not chosen_by:
|
|
continue
|
|
if entry.get("source_ref") and _named_without_platform(alias, prose):
|
|
continue
|
|
offenders.append(
|
|
f"{key}: {entry.get('name')} alias {alias} "
|
|
f"(launchers: {', '.join(sorted(chosen_by))})"
|
|
)
|
|
self.assertEqual(
|
|
offenders, [],
|
|
"an alias a platform launcher chooses must be named by the emulator's "
|
|
"code, stated in the entry's note with its source_ref, or removed",
|
|
)
|
|
|
|
def test_check_catches_a_launcher_name(self) -> None:
|
|
self.assertFalse(_named_without_platform(
|
|
"cerbios.bin", "Batocera writes bios/cerbios.bin into flashrom_path.",
|
|
))
|
|
self.assertTrue(_named_without_platform(
|
|
"cerbios.bin", "The core option offers cerbios.bin. Batocera writes it.",
|
|
))
|
|
|
|
|
|
class EntriesCiteTheEmulator(unittest.TestCase):
|
|
def test_no_source_ref_is_a_launcher_script(self) -> None:
|
|
offenders = []
|
|
for key, profile in _profiles().items():
|
|
if key in LAUNCHER_SOURCED:
|
|
continue
|
|
for entry in profile.get("files") or []:
|
|
ref = _text(entry.get("source_ref"))
|
|
if LAUNCHER_REF.search(ref):
|
|
offenders.append(f"{key}: {entry.get('name')}: {ref}")
|
|
self.assertEqual(offenders, [])
|
|
|
|
def test_known_exceptions_still_need_it(self) -> None:
|
|
profiles = _profiles()
|
|
for key in LAUNCHER_SOURCED:
|
|
refs = [_text(e.get("source_ref")) for e in profiles[key].get("files") or []]
|
|
self.assertTrue(
|
|
any(LAUNCHER_REF.search(r) for r in refs),
|
|
f"{key} no longer cites a launcher: drop it from LAUNCHER_SOURCED",
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|