Extract shared prefix matching helper
This commit is contained in:
parent
7c21310dcd
commit
68fa08d776
2 changed files with 69 additions and 54 deletions
|
|
@ -63,6 +63,42 @@ def register(defn: CommandDefinition) -> None:
|
||||||
_registry[alias] = defn
|
_registry[alias] = defn
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_prefix(command: str) -> CommandDefinition | list[str] | None:
|
||||||
|
"""Resolve a command by exact match or shortest unique prefix.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: Command name or prefix to resolve
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CommandDefinition if unique match found
|
||||||
|
list of command names if ambiguous
|
||||||
|
None if no match
|
||||||
|
"""
|
||||||
|
# Try exact match first (fast path)
|
||||||
|
defn = _registry.get(command)
|
||||||
|
if defn is not None:
|
||||||
|
return defn
|
||||||
|
|
||||||
|
# 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
|
||||||
|
return None
|
||||||
|
elif len(unique_defns) == 1:
|
||||||
|
# Unique match
|
||||||
|
return next(iter(unique_defns.values()))
|
||||||
|
else:
|
||||||
|
# Multiple matches - return list of names
|
||||||
|
return sorted([d.name for d in unique_defns.values()])
|
||||||
|
|
||||||
|
|
||||||
async def dispatch(player: Player, raw_input: str) -> None:
|
async def dispatch(player: Player, raw_input: str) -> None:
|
||||||
"""Parse input, find command, call handler.
|
"""Parse input, find command, call handler.
|
||||||
|
|
||||||
|
|
@ -80,30 +116,17 @@ 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 - exact match first (fast path)
|
# Resolve command by exact match or prefix
|
||||||
defn = _registry.get(command)
|
result = resolve_prefix(command)
|
||||||
|
|
||||||
if defn is None:
|
if result is None:
|
||||||
# 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
|
# No matches
|
||||||
player.writer.write(f"Unknown command: {command}\r\n")
|
player.writer.write(f"Unknown command: {command}\r\n")
|
||||||
await player.writer.drain()
|
await player.writer.drain()
|
||||||
return
|
return
|
||||||
elif len(unique_defns) == 1:
|
elif isinstance(result, list):
|
||||||
# Unique match
|
|
||||||
defn = next(iter(unique_defns.values()))
|
|
||||||
else:
|
|
||||||
# Multiple matches - show disambiguation
|
# Multiple matches - show disambiguation
|
||||||
names = sorted([d.name for d in unique_defns.values()])
|
names = result
|
||||||
if len(names) == 2:
|
if len(names) == 2:
|
||||||
msg = f"{names[0]} or {names[1]}?\r\n"
|
msg = f"{names[0]} or {names[1]}?\r\n"
|
||||||
else:
|
else:
|
||||||
|
|
@ -111,6 +134,9 @@ async def dispatch(player: Player, raw_input: str) -> None:
|
||||||
player.writer.write(msg)
|
player.writer.write(msg)
|
||||||
await player.writer.drain()
|
await player.writer.drain()
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
# Unique match
|
||||||
|
defn = result
|
||||||
|
|
||||||
# Check mode restriction
|
# Check mode restriction
|
||||||
if defn.mode != "*" and defn.mode != player.mode:
|
if defn.mode != "*" and defn.mode != player.mode:
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from mudlib.commands import CommandDefinition, _registry, register
|
from mudlib.commands import CommandDefinition, _registry, register, resolve_prefix
|
||||||
from mudlib.commands.movement import DIRECTIONS
|
from mudlib.commands.movement import DIRECTIONS
|
||||||
from mudlib.player import Player
|
from mudlib.player import Player
|
||||||
|
|
||||||
|
|
@ -19,31 +19,20 @@ async def _show_command_detail(player: Player, command_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
from mudlib.combat.commands import combat_moves
|
from mudlib.combat.commands import combat_moves
|
||||||
|
|
||||||
# Look up the command in registry - exact match first
|
# Resolve command by exact match or prefix
|
||||||
defn = _registry.get(command_name)
|
result = resolve_prefix(command_name)
|
||||||
|
|
||||||
# If not found, try prefix matching
|
if isinstance(result, list):
|
||||||
if defn is None:
|
|
||||||
prefix_matches = [key for key in _registry if key.startswith(command_name)]
|
|
||||||
|
|
||||||
# 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) == 1:
|
|
||||||
# Unique match via prefix
|
|
||||||
defn = next(iter(unique_defns.values()))
|
|
||||||
elif len(unique_defns) > 1:
|
|
||||||
# Multiple matches - show disambiguation
|
# Multiple matches - show disambiguation
|
||||||
names = sorted([d.name for d in unique_defns.values()])
|
names = result
|
||||||
if len(names) == 2:
|
if len(names) == 2:
|
||||||
msg = f"{names[0]} or {names[1]}?\r\n"
|
msg = f"{names[0]} or {names[1]}?\r\n"
|
||||||
else:
|
else:
|
||||||
msg = f"{', '.join(names[:-1])}, or {names[-1]}?\r\n"
|
msg = f"{', '.join(names[:-1])}, or {names[-1]}?\r\n"
|
||||||
await player.send(msg)
|
await player.send(msg)
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
defn = result
|
||||||
|
|
||||||
# If still not in registry, check if it's a combat move name (like "punch left")
|
# If still not in registry, check if it's a combat move name (like "punch left")
|
||||||
if defn is None:
|
if defn is None:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue