From feb3b03a39c57e19a30501893e74966e4ba4e122 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sat, 17 Oct 2026 12:44:22 +0200 Subject: [PATCH] fix: suggest the nearest system or emulator name --- scripts/common.py | 16 +++++++++++++++- scripts/generate_pack.py | 7 +++---- scripts/verify.py | 7 +++---- tests/test_system_selection.py | 12 ++++++++++++ 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/scripts/common.py b/scripts/common.py index a34c3b63..2234d7b5 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -350,6 +350,20 @@ def list_registered_platforms( return platforms +def suggest_names(typed: list[str], available: list[str], listing_flag: str) -> str: + """The closest known names to what was typed, and how to see them all. + + The hints showed the first ten or twenty names of the alphabet behind a + "..." whatever was typed: sony-playstaton listed 3do to apple-iie. + """ + import difflib # noqa: PLC0415 + + close = sorted({m for name in typed + for m in difflib.get_close_matches(name, available, n=3, cutoff=0.6)}) + hint = f"Did you mean: {', '.join(close)}? " if close else "" + return f"{hint}{listing_flag} lists all {len(available)}." + + def require_platform(name: str, platforms_dir: str = "platforms") -> None: """Exit with a usable message when no platform answers to a name. @@ -1345,7 +1359,7 @@ def select_emulator_profiles( ) raise ProfileSelectionError( f"emulator '{name}' not found\n" - f"Available: {', '.join(available[:10])}..." + + suggest_names([name], available, "--list-emulators") ) profile = all_profiles[name] kind = profile.get("type", "libretro") diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index aafd2c9f..a28a39c7 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -30,6 +30,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 ( + suggest_names, require_platform, apply_target_overrides, archive_entry, @@ -1646,10 +1647,8 @@ def generate_system_pack( f"No emulators found for system(s): {', '.join(system_ids)}", file=sys.stderr, ) - print( - f"Available systems: {', '.join(sorted(all_systems)[:20])}...", - file=sys.stderr, - ) + print(suggest_names(system_ids, sorted(all_systems), "--list-systems"), + file=sys.stderr) return None # Named like the platform's --system tag: one spelling of a system is diff --git a/scripts/verify.py b/scripts/verify.py index 7ea224b2..31d283f1 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -34,6 +34,7 @@ import slots sys.path.insert(0, os.path.dirname(__file__)) from common import ( + suggest_names, require_platform, archive_entry, archive_members, @@ -1849,10 +1850,8 @@ def verify_system( f"No emulators found for system(s): {', '.join(system_ids)}", file=sys.stderr, ) - print( - f"Available systems: {', '.join(sorted(all_systems)[:20])}...", - file=sys.stderr, - ) + print(suggest_names(system_ids, sorted(all_systems), "--list-systems"), + file=sys.stderr) sys.exit(1) return verify_emulator( diff --git a/tests/test_system_selection.py b/tests/test_system_selection.py index 9fc1b221..e9adfc9a 100644 --- a/tests/test_system_selection.py +++ b/tests/test_system_selection.py @@ -163,5 +163,17 @@ class ExclusionNotesListedOnAsk(unittest.TestCase): self.assertRegex(verbose, r"No external files \(\d+\):") self.assertLess(len(plain.splitlines()), len(verbose.splitlines())) + +class ATypoGetsTheNearestNames(unittest.TestCase): + """The hint listed the first names of the alphabet behind "...": + sony-playstaton was answered with 3do to apple-iie.""" + + def test_suggestions_follow_the_typed_name(self): + hint = common.suggest_names(["sony-playstaton"], + ["3do", "apple-ii", "sony-playstation"], "--list-systems") + self.assertEqual(hint, "Did you mean: sony-playstation? --list-systems lists all 3.") + self.assertEqual(common.suggest_names(["zzz"], ["3do"], "--list-systems"), + "--list-systems lists all 1.") + if __name__ == "__main__": unittest.main()