diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index abe20a00..34f6c3ae 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -1229,12 +1229,12 @@ def generate_pack( undecidable_slots=slot_undecidable, narrowings=narrowings, system_filter=system_filter, - bios_paths=[ - str(detect.get("bios_path", "")) - for detect in ( - _pack_registry.get(platform_name, {}).get("install", {}) or {} - ).get("detect", []) - ], + detect=( + _pack_registry.get(platform_name, {}).get("install", {}) or {} + ).get("detect", []), + standalone_copies=( + _pack_registry.get(platform_name, {}).get("install", {}) or {} + ).get("standalone_copies", []), ) _write_generated_member(zf, "README.txt", readme_text) diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 0bad09ca..58190c10 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -4735,6 +4735,7 @@ ZIP: extract them all into the same folder. | [RetroBat](https://www.retrobat.org/) | Windows frontend with EmulationStation, includes RetroArch and standalone emulators | [RetroBat]({rel}) | The `bios` folder inside the RetroBat installation | | [BizHawk](https://tasvideos.org/BizHawk) | Accuracy-focused multi-system emulator, popular for speedruns and TAS | [BizHawk]({rel}) | The `Firmware` folder inside the BizHawk installation | | [LaunchBox](https://www.launchbox-app.com/) | Game library manager and launcher, uses RetroArch or standalone emulators behind the scenes | [RetroArch]({rel}) | Open RetroArch (via LaunchBox) > Settings > Directory > System | +| [EmuDeck](https://www.emudeck.com/) | Installs and configures multiple emulators with ES-DE | [EmuDeck]({rel}) | `bios` in the Emulation folder chosen in EmuDeck, `C:\\Emulation\\bios\\` by default | ### Linux @@ -4750,6 +4751,7 @@ ZIP: extract them all into the same folder. | Setup | What it is | Pack | Extract to | |-------|-----------|------|-----------| | [RetroArch](https://www.retroarch.com/) | Multi-system emulator | [RetroArch]({rel}) | `~/Library/Application Support/RetroArch/system/` | +| [EmuDeck](https://www.emudeck.com/) | Installs and configures multiple emulators with ES-DE | [EmuDeck]({rel}) | `~/Emulation/bios/` | ### Raspberry Pi and single-board computers @@ -4778,7 +4780,7 @@ ZIP: extract them all into the same folder. | Setup | What it is | Pack | Extract to | |-------|-----------|------|-----------| -| [RomM](https://github.com/rommapp/romm) | Web-based ROM manager, plays games in the browser via EmulatorJS | [RomM]({rel}) | The `bios` folder in the RomM library, one subfolder per system | +| [RomM](https://github.com/rommapp/romm) | Web-based ROM manager, plays games in the browser via EmulatorJS | [RomM]({rel}) | `bios/` in the RomM library, one subfolder per platform. With `firmware: "{{platform}}/bios"` in `config.yml`, each subfolder goes to `/bios/` instead | --- diff --git a/scripts/packreadme.py b/scripts/packreadme.py index 1afc7c68..d58b74d7 100644 --- a/scripts/packreadme.py +++ b/scripts/packreadme.py @@ -5,6 +5,219 @@ goes, and which ones the user has to rename.""" from __future__ import annotations +INSTALL_SH = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh" +INSTALL_PS1 = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1" + +def _detect_systems(detect: list[dict]) -> list[str]: + """The operating systems the registry's detection rules cover, in order.""" + order = ("linux", "darwin", "windows") + seen = {str(rule.get("os", "")) for rule in detect} + return [name for name in order if name in seen] + + +def _automatic_section( + display: str, detect: list[dict], standalone_copies: list[dict] | None, +) -> str: + """The one-line install, for every system the registry detects the + platform on. Written per platform by hand, it left ROCKNIX and MiSTer + without it and told EmuDeck's Windows edition to open Konsole.""" + systems = _detect_systems(detect) + if not systems: + return "" + # A platform detected on Linux only is a system of its own (Batocera, + # ROCKNIX, MiSTer) or runs on one machine (RetroDECK, a RomM server): + # the command runs there. + on_device = systems == ["linux"] + lines = [ + " Option A: Automatic (recommended)", + " ---------------------------------", + ] + posix = [name for name in systems if name != "windows"] + if posix: + if on_device: + where = f"On the machine running {display} (its terminal, or SSH), run:" + else: + labels = {"linux": "Linux", "darwin": "macOS"} + where = f"{' and '.join(labels[name] for name in posix)}: run this in a terminal:" + lines += [f" {where}", "", f" curl -fsSL {INSTALL_SH} | sh", ""] + if "windows" in systems: + lines += [" Windows: run this in PowerShell:", "", f" irm {INSTALL_PS1} | iex", ""] + lines += [ + f" The installer finds the {display} BIOS folder on this machine and", + " installs there, checking every file. --dest names another folder.", + "", + ] + # On a desktop: an emulator folder under the home of a console's own + # system is not one its frontend reads. + if standalone_copies and not on_device: + lines += [ + " Standalone emulators read some files from folders of their own", + " (Switch and 3DS keys, PlayStation and GameCube BIOS). To copy them", + " into the emulator folders the installer finds, add --standalone-copies:", + "", + ] + if posix: + lines.append(f" curl -fsSL {INSTALL_SH} | sh -s -- --standalone-copies") + if "windows" in systems: + lines.append(f" &([scriptblock]::Create((irm {INSTALL_PS1}))) --standalone-copies") + lines.append("") + return "\n".join(lines) + "\n" + + +def _generic_manual(bios_paths: list[str]) -> str: + """Where the files go, from the registry's paths, for a platform with no + manual guide of its own.""" + targets = [path for path in bios_paths if path] + where = " or ".join(f"{path}/" for path in dict.fromkeys(targets)) or "your BIOS directory" + return ( + " Option B: Manual\n" + " ----------------\n" + f" 1. Extract all files from this archive directly into {where}\n" + " 2. Overwrite if asked\n\n" + ) + + +# What a manual install needs on each platform: its shares, cards and folders. +MANUAL_GUIDES: dict[str, str] = { + "retroarch": ( + " Option B: Manual (PC)\n" + " ---------------------\n" + " 1. Find your RetroArch system directory:\n" + " - RetroArch > Settings > Directory > System/BIOS\n" + " - Default: retroarch/system/\n" + " 2. Extract all files from this archive directly into your system directory\n" + " 3. Overwrite if asked\n\n" + " Option C: Manual (handheld / SD card)\n" + " -------------------------------------\n" + " Anbernic, Retroid, Miyoo, Trimui, etc.:\n" + " 1. Connect your SD card to your PC\n" + " 2. Find the BIOS folder (usually BIOS/ or system/)\n" + " 3. Extract all files from this archive directly into that folder\n" + " 4. Eject SD card and reboot your device\n\n" + " Common paths by device:\n" + " Anbernic (ArkOS/JELOS): BIOS/\n" + " Retroid (RetroArch): RetroArch/system/\n" + " Miyoo Mini (Onion OS): BIOS/\n" + " Steam Deck (RetroArch): ~/.config/retroarch/system/\n\n" + ), + "batocera": ( + " The terminal is F1 from the Batocera menu, or SSH.\n\n" + " Option B: Manual (network share)\n" + " --------------------------------\n" + " 1. On your PC, open the Batocera network share:\n" + " - Windows: \\\\BATOCERA\\share\\bios\\\n" + " - Mac/Linux: smb://batocera/share/bios/\n" + " 2. Extract all files from this archive directly into the share\n" + " 3. Overwrite if asked\n\n" + " Option C: Manual (SD card)\n" + " --------------------------\n" + " 1. Put the SD card in your PC\n" + " 2. Navigate to /userdata/bios/ on the SHARE partition\n" + " 3. Extract all files from this archive directly into that folder\n\n" + " NOTE: Dreamcast flash memory is named dc_nvmem.bin\n" + " (if your setup asks for dc_flash.bin, same file).\n\n" + ), + "recalbox": ( + " Option B: Manual (network share)\n" + " --------------------------------\n" + " 1. On your PC, open the Recalbox network share:\n" + " - Windows: \\\\RECALBOX\\share\\bios\\\n" + " - Mac/Linux: smb://recalbox/share/bios/\n" + " 2. Extract all files from this archive directly into the share\n\n" + " Option C: Manual (SD card)\n" + " --------------------------\n" + " 1. Put the SD card in your PC\n" + " 2. Navigate to /recalbox/share/bios/\n" + " 3. Extract all files from this archive directly into that folder\n\n" + ), + "emudeck": ( + " Option B: Manual\n" + " ----------------\n" + " Extract all files from this archive directly into EmuDeck's bios\n" + " folder, inside the Emulation folder chosen in EmuDeck:\n" + " Steam Deck, Linux and macOS: ~/Emulation/bios/ by default\n" + " Windows: C:\\Emulation\\bios\\ by default\n\n" + ), + "retrodeck": ( + " Option B: Manual\n" + " ----------------\n" + " 1. Open Dolphin file manager\n" + " 2. Show hidden files (Ctrl+H)\n" + " 3. Navigate to ~/retrodeck/\n" + " 4. Extract all files from this archive directly into ~/retrodeck/\n" + " (the archive already holds the bios/ and roms/ folders)\n\n" + " NOTE: RetroDECK uses its own BIOS checker. After\n" + " copying, open RetroDECK > Tools > BIOS Checker to\n" + " verify everything is detected.\n\n" + ), + "retrobat": ( + " Option B: Manual\n" + " ----------------\n" + " 1. Open your RetroBat installation folder\n" + " 2. Navigate to the bios\\ subfolder\n" + " (default: C:\\RetroBat\\bios\\)\n" + " 3. Extract all files from this archive directly into your bios\\ folder\n" + " 4. Overwrite if asked\n\n" + ), + "bizhawk": ( + " Option B: Manual\n" + " ----------------\n" + " 1. Open the folder you extracted BizHawk to, the one holding\n" + " EmuHawk.exe (Windows) or EmuHawkMono.sh (Linux)\n" + " 2. Navigate to its Firmware subfolder\n" + " 3. Extract all files from this archive directly into your Firmware folder\n" + " 4. In BizHawk: Config > Paths > Firmware should\n" + " point to this folder\n\n" + ), + # RomM 5.4 places firmware by filesystem.structure.firmware in config.yml, + # bios/{platform} by default, and suggests {platform}/bios for a library + # laid out as {platform}/roms (rommapp/romm 68f71259, + # backend/config/config_manager.py:58,101-109,1105-1124). + "romm": ( + " Option B: Manual\n" + " ----------------\n" + " The archive holds one folder per RomM platform (psx/, gba/, ...).\n" + " Where they go depends on filesystem.structure.firmware in RomM's\n" + " config.yml, read relative to the library folder:\n\n" + " - Not set, or \"bios/{platform}\" (the default): extract all files\n" + " from this archive directly into library/bios/\n" + " - \"{platform}/bios\" (the layout RomM suggests for a library kept as\n" + " {platform}/roms): each folder goes under its platform, psx/ into\n" + " library/psx/bios/, gba/ into library/gba/bios/, and so on.\n" + " The installer reads config.yml and does this itself. Run from\n" + " the host of a container, --romm-firmware \"{platform}/bios\" says it.\n\n" + " Then restart the RomM service to detect new files.\n\n" + ), + "retropie": ( + " Option B: Via network share\n" + " --------------------------\n" + " 1. On your PC, open: \\\\RETROPIE\\bios\\\n" + " 2. Extract all files from this archive directly into that folder\n\n" + " Option C: Via SSH\n" + " -----------------\n" + " 1. SSH into your Pi: ssh pi@retropie\n" + " 2. Copy files to ~/RetroPie/BIOS/\n\n" + " NOTE: the BIOS folder is on the card's Linux partition,\n" + " which Windows and macOS cannot open: copying through the\n" + " card works from a Linux computer only\n" + " (/home/pi/RetroPie/BIOS/).\n\n" + ), + "lakka": ( + " Lakka reads its BIOS files from /storage/system/.\n\n" + " Option B: Via network share\n" + " --------------------------\n" + " 1. On Lakka: Settings > Services > Samba Enable\n" + " 2. On your PC, open the System share: \\\\LAKKA\\System\\\n" + " (macOS and Linux: smb://lakka/System)\n" + " 3. Extract all files from this archive directly into that share\n\n" + " Option C: Via SSH\n" + " -----------------\n" + " 1. On Lakka: Settings > Services > SSH Enable\n" + " 2. Copy the files to /storage/system/ (scp -r * root@lakka:/storage/system/)\n\n" + ), +} + + def _build_readme( platform_name: str, platform_display: str, @@ -19,7 +232,8 @@ def _build_readme( undecidable_slots: list[str] | None = None, narrowings: list[tuple[str, str]] | None = None, system_filter: list[str] | None = None, - bios_paths: list[str] | None = None, + detect: list[dict] | None = None, + standalone_copies: list[dict] | None = None, ) -> str: """Build a personalized step-by-step README for each platform pack.""" narrowings = narrowings or [] @@ -31,195 +245,12 @@ def _build_readme( f"{sep}\n\n" ) - guides: dict[str, str] = { - "retroarch": ( - "INSTALLATION GUIDE\n\n" - " Option A: Automatic (recommended)\n" - " ---------------------------------\n" - " Linux, macOS, Steam Deck: run this in a terminal:\n\n" - " curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n" - " Windows: run this in PowerShell:\n\n" - " irm https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1 | iex\n\n" - " The script auto-detects your RetroArch install and copies\n" - " files to the correct location.\n\n" - " Option B: Manual (PC)\n" - " ---------------------\n" - " 1. Find your RetroArch system directory:\n" - " - RetroArch > Settings > Directory > System/BIOS\n" - " - Default: retroarch/system/\n" - " 2. Extract all files from this archive directly into your system directory\n" - " 3. Overwrite if asked\n\n" - " Option C: Manual (handheld / SD card)\n" - " -------------------------------------\n" - " Anbernic, Retroid, Miyoo, Trimui, etc.:\n" - " 1. Connect your SD card to your PC\n" - " 2. Find the BIOS folder (usually BIOS/ or system/)\n" - " 3. Extract all files from this archive directly into that folder\n" - " 4. Eject SD card and reboot your device\n\n" - " Common paths by device:\n" - " Anbernic (ArkOS/JELOS): BIOS/\n" - " Retroid (RetroArch): RetroArch/system/\n" - " Miyoo Mini (Onion OS): BIOS/\n" - " Steam Deck (RetroArch): ~/.config/retroarch/system/\n\n" - ), - "batocera": ( - "INSTALLATION GUIDE\n\n" - " Option A: Automatic (recommended)\n" - " ---------------------------------\n" - " Open a terminal (F1 from Batocera menu) and run:\n\n" - " curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n" - " Option B: Manual (network share)\n" - " --------------------------------\n" - " 1. On your PC, open the Batocera network share:\n" - " - Windows: \\\\BATOCERA\\share\\bios\\\n" - " - Mac/Linux: smb://batocera/share/bios/\n" - " 2. Extract all files from this archive directly into the share\n" - " 3. Overwrite if asked\n\n" - " Option C: Manual (SD card)\n" - " --------------------------\n" - " 1. Put the SD card in your PC\n" - " 2. Navigate to /userdata/bios/ on the SHARE partition\n" - " 3. Extract all files from this archive directly into that folder\n\n" - " NOTE: Dreamcast flash memory is named dc_nvmem.bin\n" - " (if your setup asks for dc_flash.bin, same file).\n\n" - ), - "recalbox": ( - "INSTALLATION GUIDE\n\n" - " Option A: Automatic\n" - " -------------------\n" - " curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n" - " Option B: Manual (network share)\n" - " --------------------------------\n" - " 1. On your PC, open the Recalbox network share:\n" - " - Windows: \\\\RECALBOX\\share\\bios\\\n" - " - Mac/Linux: smb://recalbox/share/bios/\n" - " 2. Extract all files from this archive directly into the share\n\n" - " Option C: Manual (SD card)\n" - " --------------------------\n" - " 1. Put the SD card in your PC\n" - " 2. Navigate to /recalbox/share/bios/\n" - " 3. Extract all files from this archive directly into that folder\n\n" - ), - "emudeck": ( - "INSTALLATION GUIDE (Steam Deck / Linux)\n\n" - " Option A: Automatic (recommended)\n" - " ---------------------------------\n" - " Open Konsole (or any terminal) and run:\n\n" - " curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n" - " The script places the BIOS files. To also copy the keys\n" - " into the standalone emulators it finds, run:\n\n" - " curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh -s -- --standalone-copies\n\n" - " Option B: Manual\n" - " ----------------\n" - " 1. Open Dolphin file manager\n" - " 2. Navigate to ~/Emulation/bios/\n" - " 3. Extract all files from this archive directly into ~/Emulation/bios/\n\n" - " STANDALONE EMULATORS (extra step)\n" - " Switch and 3DS emulators need keys in their own folders:\n" - " prod.keys -> ~/.local/share/yuzu/keys/\n" - " prod.keys -> ~/.local/share/eden/keys/\n" - " prod.keys -> ~/.config/Ryujinx/system/\n" - " aes_keys.txt -> ~/.local/share/azahar-emu/sysdata/\n" - " The installer copies them with --standalone-copies.\n\n" - ), - "retrodeck": ( - "INSTALLATION GUIDE (Steam Deck / Linux)\n\n" - " Option A: Automatic (recommended)\n" - " ---------------------------------\n" - " Open Konsole (or any terminal) and run:\n\n" - " curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n" - " Option B: Manual\n" - " ----------------\n" - " 1. Open Dolphin file manager\n" - " 2. Show hidden files (Ctrl+H)\n" - " 3. Navigate to ~/retrodeck/\n" - " 4. Extract all files from this archive directly into ~/retrodeck/\n" - " (the archive already holds the bios/ and roms/ folders)\n\n" - " NOTE: RetroDECK uses its own BIOS checker. After\n" - " copying, open RetroDECK > Tools > BIOS Checker to\n" - " verify everything is detected.\n\n" - ), - "retrobat": ( - "INSTALLATION GUIDE (Windows)\n\n" - " Option A: Automatic (recommended)\n" - " ---------------------------------\n" - " Open PowerShell and run:\n\n" - " irm https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1 | iex\n\n" - " Option B: Manual\n" - " ----------------\n" - " 1. Open your RetroBat installation folder\n" - " 2. Navigate to the bios\\ subfolder\n" - " (default: C:\\RetroBat\\bios\\)\n" - " 3. Extract all files from this archive directly into your bios\\ folder\n" - " 4. Overwrite if asked\n\n" - ), - "bizhawk": ( - "INSTALLATION GUIDE\n\n" - " 1. Open the folder you extracted BizHawk to, the one holding\n" - " EmuHawk.exe (Windows) or EmuHawkMono.sh (Linux)\n" - " 2. Navigate to its Firmware subfolder\n" - " 3. Extract all files from this archive directly into your Firmware folder\n" - " 4. In BizHawk: Config > Paths > Firmware should\n" - " point to this folder\n\n" - ), - "romm": ( - "INSTALLATION GUIDE (RomM server)\n\n" - " 1. Locate your RomM library folder\n" - " 2. Navigate to its bios/ folder (library/bios/)\n" - " 3. Extract all files from this archive directly into that folder\n" - " 4. Restart the RomM service to detect new files\n\n" - ), - "retropie": ( - "INSTALLATION GUIDE (Raspberry Pi)\n\n" - " Option A: Automatic\n" - " -------------------\n" - " On the Pi (F4 from EmulationStation, or over SSH), run:\n\n" - " curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n" - " Option B: Via network share\n" - " --------------------------\n" - " 1. On your PC, open: \\\\RETROPIE\\bios\\\n" - " 2. Extract all files from this archive directly into that folder\n\n" - " Option C: Via SSH\n" - " -----------------\n" - " 1. SSH into your Pi: ssh pi@retropie\n" - " 2. Copy files to ~/RetroPie/BIOS/\n\n" - " NOTE: the BIOS folder is on the card's Linux partition,\n" - " which Windows and macOS cannot open: copying through the\n" - " card works from a Linux computer only\n" - " (/home/pi/RetroPie/BIOS/).\n\n" - ), - } - - guides["lakka"] = ( - "INSTALLATION GUIDE (Lakka)\n\n" - " Lakka reads its BIOS files from /storage/system/.\n\n" - " Option A: Via network share\n" - " --------------------------\n" - " 1. On Lakka: Settings > Services > Samba Enable\n" - " 2. On your PC, open the System share: \\\\LAKKA\\System\\\n" - " (macOS and Linux: smb://lakka/System)\n" - " 3. Extract all files from this archive directly into that share\n\n" - " Option B: Via SSH\n" - " -----------------\n" - " 1. On Lakka: Settings > Services > SSH Enable\n" - " 2. Copy the files to /storage/system/ (scp -r * root@lakka:/storage/system/)\n\n" - ) - - # A platform without a written guide is told where its files go by the - # registry, which the installer reads too: "your BIOS directory" sent - # MiSTer users looking for a folder MiSTer does not have. - targets = [path for path in bios_paths or [] if path] - where = ( - " or ".join(f"{path}/" for path in targets) - if targets else "your BIOS directory" - ) - guide = guides.get( - platform_name, - ( - f"INSTALLATION\n\n" - f" 1. Extract all files from this archive directly into {where}\n" - f" 2. Overwrite if asked\n\n" - ), + detect = list(detect or []) + bios_paths = [str(rule.get("bios_path", "")) for rule in detect] + guide = ( + "INSTALLATION GUIDE\n\n" + + _automatic_section(platform_display, detect, standalone_copies) + + MANUAL_GUIDES.get(platform_name, _generic_manual(bios_paths)) ) if regions: diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index d12efd68..e9bb9d5a 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -617,7 +617,11 @@ class PackReadmesSayWhatTheInstallerDoes(unittest.TestCase): def _guide(self, platform: str) -> str: from packreadme import _build_readme - text = _build_readme(platform, platform, "", 1, 1) + registry = yaml.safe_load((ROOT / "platforms" / "_registry.yml").read_text()) + install = registry["platforms"][platform].get("install") or {} + text = _build_readme(platform, platform, "", 1, 1, + detect=install.get("detect", []), + standalone_copies=install.get("standalone_copies", [])) return text[text.index("INSTALLATION"):text.index("TROUBLESHOOTING")] def test_retroarch_offers_the_windows_installer(self): @@ -642,7 +646,6 @@ class PackReadmesSayWhatTheInstallerDoes(unittest.TestCase): named = { path.rstrip("/") for path in re.findall(r"-> (~/\S+)", guide) } - self.assertTrue(named) self.assertEqual(sorted(named - targets), []) def test_retropie_never_sends_a_pc_to_the_linux_partition(self): @@ -3832,7 +3835,8 @@ class EveryReadmeNamesItsFolder(unittest.TestCase): if not folder: continue with self.subTest(platform=name): - text = _build_readme(name, name, "", 1, 1, bios_paths=[folder]) + text = _build_readme(name, name, "", 1, 1, + detect=[{"os": "linux", "bios_path": folder}]) self.assertIn(folder, text) lakka = _build_readme("lakka", "Lakka", "", 1, 1) self.assertNotIn("retroarch/system", lakka) diff --git a/tests/test_doc_surfaces.py b/tests/test_doc_surfaces.py index 6d825714..4f948123 100644 --- a/tests/test_doc_surfaces.py +++ b/tests/test_doc_surfaces.py @@ -71,6 +71,26 @@ def _which_pack() -> str: return generate_site.generate_which_pack() +class TheDownloadTablesFollowTheRegistry(unittest.TestCase): + """EmuDeck has a Windows edition the installer detects, and the Windows + table of the download page left it out.""" + + def test_every_detected_system_lists_the_platform(self): + import yaml # noqa: PLC0415 + + registry = yaml.safe_load(_read("platforms/_registry.yml")) + names = {"retroarch": "RetroArch", "retrobat": "RetroBat", "emudeck": "EmuDeck", + "bizhawk": "BizHawk"} + page = _which_pack() + for os_name, heading in (("windows", "### Windows"), ("darwin", "### macOS")): + table = _section(page, heading) + for platform, entry in registry["platforms"].items(): + systems = {rule["os"] for rule in (entry.get("install") or {}).get("detect", [])} + if os_name in systems: + with self.subTest(os=os_name, platform=platform): + self.assertIn(f"[{names[platform]}](", table) + + class DownloadLinksNameTheFile(unittest.TestCase): def test_a_whole_pack_links_its_asset(self): self.assertEqual( diff --git a/tests/test_pack_readme.py b/tests/test_pack_readme.py index 26f04fe3..4e122e46 100644 --- a/tests/test_pack_readme.py +++ b/tests/test_pack_readme.py @@ -34,13 +34,10 @@ class GuidesPointAtTheReadPath(unittest.TestCase): def test_unguided_platforms_name_the_registry_path(self): registry = yaml.safe_load((REPO_ROOT / "platforms" / "_registry.yml").read_text()) for name, data in registry["platforms"].items(): - paths = [ - str(d.get("bios_path", "")) - for d in (data.get("install", {}) or {}).get("detect", []) - ] - if not any(paths): + detect = (data.get("install", {}) or {}).get("detect", []) + if not any(d.get("bios_path") for d in detect): continue - text = _build_readme(name, name, "", 1, 1, bios_paths=paths) + text = _build_readme(name, name, "", 1, 1, detect=detect) with self.subTest(platform=name): self.assertFalse( any("your BIOS directory" in line for line in _extract_lines(text)) @@ -55,5 +52,56 @@ class GuidesNameShippedInstallers(unittest.TestCase): with self.subTest(installer=name): self.assertTrue((REPO_ROOT / name).is_file()) +def _registry() -> dict: + return yaml.safe_load((REPO_ROOT / "platforms" / "_registry.yml").read_text()) + + +def _guide(name: str) -> str: + install = _registry()["platforms"][name].get("install") or {} + return _build_readme(name, name, "", 1, 1, detect=install.get("detect", []), + standalone_copies=install.get("standalone_copies", [])) + + +class TheOneLinerFollowsTheRegistry(unittest.TestCase): + """The guides were written per platform by hand: ROCKNIX and MiSTer, + which the installer detects, had no one-line install, EmuDeck's + Windows edition was told to open Konsole, and every RomM user was sent + to library/bios/ whatever its firmware template.""" + + def test_every_detected_system_gets_its_command(self): + for name, data in _registry()["platforms"].items(): + systems = {rule["os"] for rule in (data.get("install") or {}).get("detect", [])} + if not systems: + continue + text = _guide(name) + with self.subTest(platform=name): + self.assertEqual("install.sh | sh" in text, bool(systems - {"windows"})) + self.assertEqual("install.ps1 | iex" in text, "windows" in systems) + + def test_rocknix_and_mister_run_it_on_the_device(self): + for name in ("rocknix", "misterfpga"): + with self.subTest(platform=name): + self.assertIn(f"On the machine running {name}", _guide(name)) + + def test_emudeck_is_not_steam_deck_only(self): + text = _guide("emudeck") + self.assertNotIn("Steam Deck / Linux", text) + self.assertNotIn("Konsole", text) + self.assertIn("install.ps1 | iex", text) + self.assertIn("C:\\Emulation\\bios", text) + self.assertIn("--standalone-copies", text) + + def test_romm_names_both_firmware_layouts(self): + text = _guide("romm") + self.assertIn("library/bios/", text) + self.assertIn("library/psx/bios/", text) + self.assertIn("--romm-firmware", text) + + def test_the_commands_are_written_once(self): + source = (REPO_ROOT / "scripts" / "packreadme.py").read_text(encoding="utf-8") + self.assertEqual(source.count("main/install.sh"), 1) + self.assertEqual(source.count("main/install.ps1"), 1) + + if __name__ == "__main__": unittest.main()