diff --git a/install.ps1 b/install.ps1 index 8bb01c51..3f26cd07 100644 --- a/install.ps1 +++ b/install.ps1 @@ -11,7 +11,7 @@ $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" - $defaultInstallSha256 = "df46e48289c89c5c542095b01c16208ef54696df472685c99327e5022fcd7ab8" + $defaultInstallSha256 = "2409cff28ad4eed1546161aa5abd6808fcd6a010bda7943eb1076f4ab0559c9f" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index cba224d1..e3dae02f 100644 --- a/install.py +++ b/install.py @@ -2408,7 +2408,8 @@ def resolve_choices(values: list[str], known: list[str], label: str) -> list[str if unknown: if not known: raise ValueError(f"unknown {label}: {', '.join(unknown)}. This list records none.") - listing = "--list-systems" if label == "system" else "--list-cores" + listing = {"system": "--list-systems", "emulator": "--list-emulators"}.get( + label, "--list-cores") raise ValueError( f"unknown {label}: {', '.join(unknown)}. {did_you_mean(unknown, known, listing)}" ) @@ -3923,6 +3924,33 @@ def _print_choices( print(f" {name:<{width}} {_sized(held)}") +def emulator_names(manifest: dict, files: list[dict], omitted: list[dict]) -> dict[str, str]: + """Every name --emulator accepts, with the profile each one stands for. + + A profile answers to its own name and to the names its core is built + under (core_names: beetle_psx and mednafen_psx). A core named by a file + and by no profile stands for itself. + """ + names = {core: core for core in available_choices(files, omitted)["cores"]} + for profile, aliases in (manifest.get("core_names") or {}).items(): + names[profile] = profile + for alias in aliases: + names.setdefault(alias, profile) + return names + + +def _print_emulators(plat: str, manifest: dict, files: list[dict], omitted: list[dict]) -> None: + """What --emulator accepts for a platform, one name per line.""" + names = emulator_names(manifest, files, omitted) + if not names: + print(f" {plat} records no emulators") + return + width = min(max(len(name) for name in names), 40) + for name in sorted(names): + profile = names[name] + print(f" {name:<{width}} {'' if profile == name else 'same as ' + profile}".rstrip()) + + def _older_list_advice() -> str: """How to reach a file list that records what an option needs. @@ -3952,11 +3980,7 @@ def _narrow_to_emulators( "which systems, which --emulator needs.", file=sys.stderr) print(_older_list_advice(), file=sys.stderr) sys.exit(1) - known = sorted({ - name - for profile, aliases in (manifest.get("core_names") or {}).items() - for name in [profile, *aliases] - } | set(available_choices(files, omitted)["cores"])) + known = sorted(emulator_names(manifest, files, omitted)) try: chosen = resolve_choices(names, known, "emulator") except ValueError as exc: @@ -4093,7 +4117,7 @@ def main() -> None: action="append", metavar="NAME", help="install what these emulators read on the platform, as a pack " - "built for them carries (names: --list-cores)", + "built for them carries (names: --list-emulators)", ) parser.add_argument( "--installed-cores", @@ -4138,6 +4162,11 @@ def main() -> None: action="store_true", help="list the cores whose extra files a platform installs and exit", ) + parser.add_argument( + "--list-emulators", + action="store_true", + help="list every name --emulator accepts for a platform and exit", + ) parser.add_argument( "--no-input", action="store_true", @@ -4183,7 +4212,7 @@ def main() -> None: parser.error(f"{option} needs at least one name") if args.quiet and ( args.check or args.dry_run or args.list_platforms or args.list_targets - or args.list_systems or args.list_cores + or args.list_systems or args.list_cores or args.list_emulators ): parser.error("--quiet applies to an install, not to --check, --dry-run or a listing") if args.every_board and args.target: @@ -4271,7 +4300,8 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None: """One run of the installer, once the options are read.""" term = Terminal(real_stdout, quiet=args.quiet) interactive = sys.stdin.isatty() and not args.no_input and not args.quiet - listing = args.list_targets or args.list_systems or args.list_cores + listing = (args.list_targets or args.list_systems or args.list_cores + or args.list_emulators) print(f"{term.bold('RetroBIOS')} {term.dim(installer_id())}\n") os_type = detect_os() @@ -4500,6 +4530,10 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None: ) print(f" Narrowed {before} -> {len(files)} files to the installed cores") + if args.list_emulators: + _print_emulators(plat_name, manifest, files, omitted_files) + continue + if args.emulator: files, omitted_files = _narrow_to_emulators( args.emulator, manifest, plat_name, files, omitted_files diff --git a/install.sh b/install.sh index de28778f..d7a4c4d5 100755 --- a/install.sh +++ b/install.sh @@ -18,7 +18,7 @@ esac TEMP_INSTALLER="" TEMP_DIRECTORY="" DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -DEFAULT_INSTALL_SHA256="df46e48289c89c5c542095b01c16208ef54696df472685c99327e5022fcd7ab8" +DEFAULT_INSTALL_SHA256="2409cff28ad4eed1546161aa5abd6808fcd6a010bda7943eb1076f4ab0559c9f" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_install.py b/tests/test_install.py index 49b949c7..06b714f3 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -3711,5 +3711,41 @@ class ACompletePartialIsHandedOffOnWindows(unittest.TestCase): self.assertTrue(install._write_refused(PermissionError(13, "denied"))) +class TheEmulatorNamesHaveTheirOwnListing(unittest.TestCase, _LoopbackRun): + """--emulator nope said "--list-cores lists all 441" and --list-cores + printed 189: the buildbot names --emulator accepts were listed nowhere.""" + + def setUp(self): + self.env = self.serve(self) + + def test_the_hint_counts_what_the_listing_prints(self): + proc = _install_run(self.env, "--platform", "retroarch", "--no-input", + "--emulator", "nope") + self.assertEqual(proc.returncode, 1, proc.stdout) + found = re.search(r"--list-emulators lists all (\d+)\.", proc.stderr) + self.assertIsNotNone(found, proc.stderr) + listing = _install_run(self.env, "--platform", "retroarch", "--no-input", + "--list-emulators") + self.assertEqual(listing.returncode, 0, listing.stderr) + names = [re.split(r"\s{2,}", line.strip())[0] + for line in listing.stdout.splitlines() if line.startswith(" ")] + manifest = json.loads((REPO_ROOT / "install" / "retroarch.json").read_text()) + accepted = install.emulator_names( + manifest, manifest["files"], manifest.get("omitted_files", [])) + self.assertEqual(int(found.group(1)), len(accepted)) + self.assertTrue(set(accepted) <= set(names), set(accepted) - set(names)) + self.assertIn("mednafen_psx", accepted) + self.assertRegex(listing.stdout, r"mednafen_psx\s+same as beetle_psx") + + def test_every_listed_name_is_accepted(self): + manifest = {"core_names": {"beetle_psx": ["mednafen_psx"]}} + files = [{"dest": "a", "cores": ["pcsx_rearmed"]}] + names = install.emulator_names(manifest, files, []) + self.assertEqual(names, {"beetle_psx": "beetle_psx", "mednafen_psx": "beetle_psx", + "pcsx_rearmed": "pcsx_rearmed"}) + self.assertEqual(install.resolve_choices(["MEDNAFEN_PSX"], sorted(names), "emulator"), + ["mednafen_psx"]) + + if __name__ == "__main__": unittest.main() diff --git a/wiki/installer.md b/wiki/installer.md index c8349519..e8b6b3c8 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -230,10 +230,11 @@ what the options name, everything by default. | `--cores-dir PATH` | The folder RetroArch loads its cores from, for `--installed-cores` when it is not found. Implies `--installed-cores` | | `--system NAMES` | Install only these systems. Comma-separated or repeated. `--list-systems` prints the names, `other` included | | `--core NAMES` | Keep the platform's own list and only the extra files these cores load. `--list-cores` prints the names | -| `--emulator NAMES` | Install what these emulators read on the detected platform, and nothing else: their own files and the platform's files of the systems they run. Each emulator page on the site gives this command. `--list-cores` prints the names | +| `--emulator NAMES` | Install what these emulators read on the detected platform, and nothing else: their own files and the platform's files of the systems they run. Each emulator page on the site gives this command. `--list-emulators` prints the names, buildbot names such as `mednafen_psx` included | | `--region LIST` | Preferred regions, best first (`us,eu,jp`): keep one BIOS per region where a system has several. Accepts the names and aliases of `generate_pack.py --region` | | `--list-systems` | Print a platform's systems with the files and size each holds | | `--list-cores` | Print the cores whose extra files a platform installs | +| `--list-emulators` | Print every name `--emulator` accepts, each buildbot name beside the emulator it stands for | | `--no-input` | Never ask a question, even at a terminal | | `--check` | Report and exit without writing | | `--dry-run` | Show what would be downloaded, by system and with sizes, without writing anything |