Implement collision detection in register
This commit is contained in:
parent
5efdbaf4e6
commit
7d3b02f6ff
1 changed files with 55 additions and 4 deletions
|
|
@ -1,10 +1,13 @@
|
||||||
"""Command registry and dispatcher."""
|
"""Command registry and dispatcher."""
|
||||||
|
|
||||||
|
import logging
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from mudlib.player import Player
|
from mudlib.player import Player
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Type alias for command handlers
|
# Type alias for command handlers
|
||||||
CommandHandler = Callable[[Player, str], Awaitable[None]]
|
CommandHandler = Callable[[Player, str], Awaitable[None]]
|
||||||
|
|
||||||
|
|
@ -31,8 +34,32 @@ def register(defn: CommandDefinition) -> None:
|
||||||
Args:
|
Args:
|
||||||
defn: The command definition to register
|
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
|
_registry[defn.name] = defn
|
||||||
|
|
||||||
|
# Check for collisions on each alias
|
||||||
for alias in defn.aliases:
|
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
|
_registry[alias] = defn
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,13 +80,37 @@ async def dispatch(player: Player, raw_input: str) -> None:
|
||||||
command = parts[0].lower()
|
command = parts[0].lower()
|
||||||
args = parts[1] if len(parts) > 1 else ""
|
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)
|
defn = _registry.get(command)
|
||||||
|
|
||||||
if defn is None:
|
if defn is None:
|
||||||
player.writer.write(f"Unknown command: {command}\r\n")
|
# Try prefix matching
|
||||||
await player.writer.drain()
|
prefix_matches = [key for key in _registry if key.startswith(command)]
|
||||||
return
|
|
||||||
|
# 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
|
# Check mode restriction
|
||||||
if defn.mode != "*" and defn.mode != player.mode:
|
if defn.mode != "*" and defn.mode != player.mode:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue