From 7d3b02f6ff47ee5d3e8973095f3510a9f77cabd9 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Sun, 8 Feb 2026 13:33:19 -0500 Subject: [PATCH] Implement collision detection in register --- src/mudlib/commands/__init__.py | 59 ++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/mudlib/commands/__init__.py b/src/mudlib/commands/__init__.py index 09b2fd0..7a3d47e 100644 --- a/src/mudlib/commands/__init__.py +++ b/src/mudlib/commands/__init__.py @@ -1,10 +1,13 @@ """Command registry and dispatcher.""" +import logging from collections.abc import Awaitable, Callable from dataclasses import dataclass, field from mudlib.player import Player +logger = logging.getLogger(__name__) + # Type alias for command handlers CommandHandler = Callable[[Player, str], Awaitable[None]] @@ -31,8 +34,32 @@ def register(defn: CommandDefinition) -> None: Args: defn: The command definition to register """ + # Check for collision on the main name + if defn.name in _registry: + existing = _registry[defn.name] + if existing is not defn: + logger.warning( + "Command collision: '%s' already registered for '%s', " + "overwriting with '%s'", + defn.name, + existing.name, + defn.name, + ) + _registry[defn.name] = defn + + # Check for collisions on each alias for alias in defn.aliases: + if alias in _registry: + existing = _registry[alias] + if existing is not defn: + logger.warning( + "Command collision: '%s' already registered for '%s', " + "overwriting with '%s'", + alias, + existing.name, + defn.name, + ) _registry[alias] = defn @@ -53,13 +80,37 @@ async def dispatch(player: Player, raw_input: str) -> None: command = parts[0].lower() args = parts[1] if len(parts) > 1 else "" - # Look up the definition + # Look up the definition - exact match first (fast path) defn = _registry.get(command) if defn is None: - player.writer.write(f"Unknown command: {command}\r\n") - await player.writer.drain() - return + # Try prefix matching + prefix_matches = [key for key in _registry if key.startswith(command)] + + # Deduplicate by CommandDefinition identity + unique_defns = {} + for key in prefix_matches: + defn_obj = _registry[key] + unique_defns[id(defn_obj)] = defn_obj + + if len(unique_defns) == 0: + # No matches + player.writer.write(f"Unknown command: {command}\r\n") + await player.writer.drain() + return + elif len(unique_defns) == 1: + # Unique match + defn = next(iter(unique_defns.values())) + else: + # Multiple matches - show disambiguation + names = sorted([d.name for d in unique_defns.values()]) + if len(names) == 2: + msg = f"{names[0]} or {names[1]}?\r\n" + else: + msg = f"{', '.join(names[:-1])}, or {names[-1]}?\r\n" + player.writer.write(msg) + await player.writer.drain() + return # Check mode restriction if defn.mode != "*" and defn.mode != player.mode: