diff --git a/scripts/auto_fetch.py b/scripts/auto_fetch.py index 4f3dbf69..3704a5c4 100644 --- a/scripts/auto_fetch.py +++ b/scripts/auto_fetch.py @@ -29,6 +29,7 @@ from pathlib import Path sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from common import ( + platform_arg, require_directory, list_registered_platforms, load_database, @@ -418,7 +419,7 @@ def generate_issue_body(missing: list[dict], platform: str) -> str: def main() -> None: parser = argparse.ArgumentParser(allow_abbrev=False, description="Auto-fetch missing BIOS files") - parser.add_argument("--platform", "-p", help="Platform to check") + parser.add_argument("--platform", "-p", type=platform_arg, help="Platform to check") parser.add_argument("--all", action="store_true", help="Check all platforms") parser.add_argument( "--dry-run", action="store_true", help="Don't download, just report" diff --git a/scripts/common.py b/scripts/common.py index dd9daac7..df830466 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -449,6 +449,24 @@ def suggest_names(typed: list[str], available: list[str], listing_flag: str) -> return f"{listing_flag} lists all {len(available)}." +def platform_arg(value: str) -> str: + """--platform as typed, mapped to the registry's slug when it names one. + + The site and the pack names say RetroArch and MiSTer FPGA; verify and + generate_pack refused both while install.py took them. Letters and + digits decide, as for pack names; anything else reaches require_platform + unchanged and gets its suggestion. + """ + key = re.sub(r"[^a-z0-9]", "", value.lower()) + if yaml is None or not os.path.isdir("platforms"): + return value + known = list_registered_platforms("platforms", include_archived=True) + for name in known: + if re.sub(r"[^a-z0-9]", "", name.lower()) == key: + return name + return value + + def require_platform(name: str, platforms_dir: str = "platforms") -> None: """Exit with a usable message when no platform answers to a name. diff --git a/scripts/cross_reference.py b/scripts/cross_reference.py index 61c82ffe..fcc09057 100644 --- a/scripts/cross_reference.py +++ b/scripts/cross_reference.py @@ -23,6 +23,7 @@ from pathlib import Path sys.path.insert(0, os.path.dirname(__file__)) from common import ( + platform_arg, ProfileSelectionError, _norm_system_id, directory_members, @@ -761,7 +762,7 @@ def main() -> None: parser.add_argument("--db", default=DEFAULT_DB) parser.add_argument("--emulator", "-e", help="Analyze single emulator") parser.add_argument( - "--platform", "-p", help="Restrict analysis to one platform's cores" + "--platform", "-p", type=platform_arg, help="Restrict analysis to one platform's cores" ) parser.add_argument("--target", "-t", help="Hardware target (e.g., switch, rpi4)") parser.add_argument("--json", action="store_true", help="JSON output") diff --git a/scripts/diff_truth.py b/scripts/diff_truth.py index 916e845e..a9a8cf20 100644 --- a/scripts/diff_truth.py +++ b/scripts/diff_truth.py @@ -17,6 +17,7 @@ import sys sys.path.insert(0, os.path.dirname(__file__)) from common import ( + platform_arg, list_registered_platforms, load_platform_config, native_layer, @@ -167,7 +168,7 @@ def main() -> None: group.add_argument( "--all", action="store_true", help="diff all registered platforms" ) - group.add_argument("--platform", help="diff a single platform") + group.add_argument("--platform", type=platform_arg, help="diff a single platform") # One output form: --json beside --format markdown printed JSON and # dropped the markdown without a word. output = parser.add_mutually_exclusive_group() diff --git a/scripts/export_native.py b/scripts/export_native.py index 96200beb..a6fa3692 100644 --- a/scripts/export_native.py +++ b/scripts/export_native.py @@ -29,6 +29,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent)) from artifacts import file_lock, write_bytes_atomic, write_text_atomic from common import ( + platform_arg, list_registered_platforms, load_platform_config, native_layer, @@ -513,7 +514,7 @@ def main() -> None: ) group = parser.add_mutually_exclusive_group(required=True) group.add_argument("--all", action="store_true", help="every platform") - group.add_argument("--platform", help="a single platform") + group.add_argument("--platform", type=platform_arg, help="a single platform") parser.add_argument("--output-dir", default="dist/upstream") parser.add_argument("--truth-dir", default="dist/truth") parser.add_argument("--platforms-dir", default="platforms") diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index d1320b53..7238dd88 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -33,6 +33,7 @@ from pathlib import Path sys.path.insert(0, os.path.dirname(__file__)) from common import resolve_platform_cores as _platform_cores from common import ( + platform_arg, require_directory, target_listing_line, suggest_names, @@ -2874,7 +2875,7 @@ def main() -> None: def _main(held: contextlib.ExitStack) -> None: parser = argparse.ArgumentParser(allow_abbrev=False, description="Generate platform BIOS ZIP packs") - parser.add_argument("--platform", "-p", help="Platform name (e.g., retroarch)") + parser.add_argument("--platform", "-p", type=platform_arg, help="Platform name (e.g., retroarch)") parser.add_argument( "--all", action="store_true", help="Generate packs for all active platforms" ) diff --git a/scripts/generate_truth.py b/scripts/generate_truth.py index 236ddab6..e83ad6b7 100644 --- a/scripts/generate_truth.py +++ b/scripts/generate_truth.py @@ -14,6 +14,7 @@ import sys sys.path.insert(0, os.path.dirname(__file__)) from common import ( + platform_arg, list_registered_platforms, load_database, load_emulator_profiles, @@ -42,7 +43,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: ) group = parser.add_mutually_exclusive_group(required=True) group.add_argument("--all", action="store_true", help="all registered platforms") - group.add_argument("--platform", help="single platform name") + group.add_argument("--platform", type=platform_arg, help="single platform name") parser.add_argument( "--output-dir", default=DEFAULT_OUTPUT_DIR, diff --git a/scripts/refresh_data_dirs.py b/scripts/refresh_data_dirs.py index e36f865e..97e455e3 100644 --- a/scripts/refresh_data_dirs.py +++ b/scripts/refresh_data_dirs.py @@ -25,7 +25,7 @@ import urllib.request import zipfile from pathlib import Path from artifacts import file_lock as _file_lock -from common import require_platform, yaml_load +from common import platform_arg, require_platform, yaml_load try: import yaml @@ -528,7 +528,7 @@ def main() -> None: parser.add_argument( "--dry-run", action="store_true", help="Preview without downloading" ) - parser.add_argument("--platform", help="Only refresh entries for this platform") + parser.add_argument("--platform", type=platform_arg, help="Only refresh entries for this platform") parser.add_argument( "--registry", default=DEFAULT_REGISTRY, help="Path to _data_dirs.yml" ) diff --git a/scripts/slots.py b/scripts/slots.py index 64c296db..9b22e28e 100644 --- a/scripts/slots.py +++ b/scripts/slots.py @@ -21,6 +21,7 @@ from typing import TYPE_CHECKING import nativemode from common import ( + platform_arg, archive_entry, build_zip_contents_index, check_inside_zip, @@ -818,7 +819,7 @@ def main() -> int: description="Report destinations where an emulator profile contradicts " "the file a platform baseline would ship.", ) - parser.add_argument("--platform", help="one platform instead of all") + parser.add_argument("--platform", type=platform_arg, help="one platform instead of all") parser.add_argument("--db", default="database.json") parser.add_argument("--platforms-dir", default="platforms") parser.add_argument("--emulators-dir", default="emulators") diff --git a/scripts/verify.py b/scripts/verify.py index 40704888..c9350a56 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -35,6 +35,7 @@ import slots sys.path.insert(0, os.path.dirname(__file__)) from common import ( + platform_arg, target_listing_line, suggest_names, require_platform, @@ -2106,7 +2107,7 @@ def main() -> None: def _main(held: contextlib.ExitStack) -> None: parser = argparse.ArgumentParser(allow_abbrev=False, description="Platform-native BIOS verification") - parser.add_argument("--platform", "-p", help="Platform name") + parser.add_argument("--platform", "-p", type=platform_arg, help="Platform name") parser.add_argument( "--all", action="store_true", help="Verify all active platforms" ) diff --git a/tests/test_system_selection.py b/tests/test_system_selection.py index 00559e43..6fdc8ef7 100644 --- a/tests/test_system_selection.py +++ b/tests/test_system_selection.py @@ -128,6 +128,24 @@ class AMistypedPlatformIsNamed(unittest.TestCase): self.assertIn("Did you mean 'batocera'?", proc.stderr) self.assertFalse(out.exists()) + def test_the_names_the_site_prints_are_accepted(self): + """`--platform RetroArch` and `--platform "MiSTer FPGA"` were refused + by verify and generate_pack while install.py took them.""" + from common import platform_arg # noqa: PLC0415 + + previous = os.getcwd() + os.chdir(REPO_ROOT) + self.addCleanup(os.chdir, previous) + self.assertEqual(platform_arg("RetroArch"), "retroarch") + self.assertEqual(platform_arg("MiSTer FPGA"), "misterfpga") + self.assertEqual(platform_arg("retroarh"), "retroarh") + for name in [p.name for p in (REPO_ROOT / "scripts").glob("*.py") + if re.search(r'add_argument\(\s*"--platform"', p.read_text(encoding="utf-8"))]: + with self.subTest(script=name): + source = (REPO_ROOT / "scripts" / name).read_text(encoding="utf-8") + call = re.search(r'add_argument\(\s*"--platform"[^)]*\)', source).group(0) + self.assertIn("type=platform_arg", call) + def test_the_list_holds_every_script_taking_a_platform(self): taking = { path.name for path in (REPO_ROOT / "scripts").glob("*.py")