Add CommandDefinition and migrate command registry
This commit is contained in:
parent
bea2a73c98
commit
dcc8b961bb
7 changed files with 65 additions and 46 deletions
|
|
@ -1,15 +1,25 @@
|
||||||
command system
|
command system
|
||||||
==============
|
==============
|
||||||
|
|
||||||
commands are registered in a simple dict mapping names to async handlers.
|
commands are registered as CommandDefinition objects with metadata.
|
||||||
|
|
||||||
|
from mudlib.commands import CommandDefinition, register
|
||||||
|
|
||||||
async def my_command(player: Player, args: str) -> None:
|
async def my_command(player: Player, args: str) -> None:
|
||||||
player.writer.write("hello\r\n")
|
player.writer.write("hello\r\n")
|
||||||
await player.writer.drain()
|
await player.writer.drain()
|
||||||
|
|
||||||
register("mycommand", my_command, aliases=["mc"])
|
register(CommandDefinition("mycommand", my_command, aliases=["mc"]))
|
||||||
|
|
||||||
dispatch parses input, looks up the handler, calls it:
|
CommandDefinition fields:
|
||||||
|
|
||||||
|
name primary command name
|
||||||
|
handler async function(player, args)
|
||||||
|
aliases alternative names (default: [])
|
||||||
|
mode required player mode (default: "normal", "*" = any mode)
|
||||||
|
help help text (default: "")
|
||||||
|
|
||||||
|
dispatch parses input, looks up the definition, calls its handler:
|
||||||
|
|
||||||
await dispatch(player, "mycommand some args")
|
await dispatch(player, "mycommand some args")
|
||||||
|
|
||||||
|
|
@ -43,16 +53,17 @@ adding commands
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
1. create src/mudlib/commands/yourcommand.py
|
1. create src/mudlib/commands/yourcommand.py
|
||||||
2. import register from mudlib.commands
|
2. import CommandDefinition and register from mudlib.commands
|
||||||
3. define async handler(player, args)
|
3. define async handler(player, args)
|
||||||
4. call register() with name and aliases
|
4. call register(CommandDefinition(...))
|
||||||
5. import the module in server.py so registration runs at startup
|
5. import the module in server.py so registration runs at startup
|
||||||
|
|
||||||
code
|
code
|
||||||
----
|
----
|
||||||
|
|
||||||
src/mudlib/commands/__init__.py registry + dispatch
|
src/mudlib/commands/__init__.py registry + dispatch + CommandDefinition
|
||||||
src/mudlib/commands/movement.py direction commands
|
src/mudlib/commands/movement.py direction commands
|
||||||
src/mudlib/commands/look.py look/l
|
src/mudlib/commands/look.py look/l
|
||||||
src/mudlib/commands/quit.py quit/q
|
src/mudlib/commands/fly.py fly
|
||||||
|
src/mudlib/commands/quit.py quit/q (mode="*")
|
||||||
src/mudlib/player.py Player dataclass + registry
|
src/mudlib/player.py Player dataclass + registry
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,38 @@
|
||||||
"""Command registry and dispatcher."""
|
"""Command registry and dispatcher."""
|
||||||
|
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from mudlib.player import Player
|
from mudlib.player import Player
|
||||||
|
|
||||||
# Type alias for command handlers
|
# Type alias for command handlers
|
||||||
CommandHandler = Callable[[Player, str], Awaitable[None]]
|
CommandHandler = Callable[[Player, str], Awaitable[None]]
|
||||||
|
|
||||||
# Registry maps command names to handler functions
|
|
||||||
_registry: dict[str, CommandHandler] = {}
|
@dataclass
|
||||||
|
class CommandDefinition:
|
||||||
|
"""Metadata wrapper for a registered command."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
handler: CommandHandler
|
||||||
|
aliases: list[str] = field(default_factory=list)
|
||||||
|
mode: str = "normal"
|
||||||
|
help: str = ""
|
||||||
|
|
||||||
|
|
||||||
def register(
|
# Registry maps command names to definitions
|
||||||
name: str, handler: CommandHandler, aliases: list[str] | None = None
|
_registry: dict[str, CommandDefinition] = {}
|
||||||
) -> None:
|
|
||||||
"""Register a command handler with optional aliases.
|
|
||||||
|
def register(defn: CommandDefinition) -> None:
|
||||||
|
"""Register a command definition with its aliases.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: The primary command name
|
defn: The command definition to register
|
||||||
handler: Async function that handles the command
|
|
||||||
aliases: Optional list of alternative names for the command
|
|
||||||
"""
|
"""
|
||||||
_registry[name] = handler
|
_registry[defn.name] = defn
|
||||||
|
for alias in defn.aliases:
|
||||||
if aliases:
|
_registry[alias] = defn
|
||||||
for alias in aliases:
|
|
||||||
_registry[alias] = handler
|
|
||||||
|
|
||||||
|
|
||||||
async def dispatch(player: Player, raw_input: str) -> None:
|
async def dispatch(player: Player, raw_input: str) -> None:
|
||||||
|
|
@ -45,13 +52,13 @@ 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 handler
|
# Look up the definition
|
||||||
handler = _registry.get(command)
|
defn = _registry.get(command)
|
||||||
|
|
||||||
if handler is None:
|
if defn is None:
|
||||||
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
|
||||||
|
|
||||||
# Execute the handler
|
# Execute the handler
|
||||||
await handler(player, args)
|
await defn.handler(player, args)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from mudlib.commands import register
|
from mudlib.commands import CommandDefinition, register
|
||||||
from mudlib.commands.movement import DIRECTIONS, send_nearby_message
|
from mudlib.commands.movement import DIRECTIONS, send_nearby_message
|
||||||
from mudlib.effects import add_effect
|
from mudlib.effects import add_effect
|
||||||
from mudlib.player import Player
|
from mudlib.player import Player
|
||||||
|
|
@ -91,4 +91,4 @@ async def cmd_fly(player: Player, args: str) -> None:
|
||||||
await cmd_look(player, "")
|
await cmd_look(player, "")
|
||||||
|
|
||||||
|
|
||||||
register("fly", cmd_fly)
|
register(CommandDefinition("fly", cmd_fly))
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from mudlib.commands import register
|
from mudlib.commands import CommandDefinition, register
|
||||||
from mudlib.effects import get_effects_at
|
from mudlib.effects import get_effects_at
|
||||||
from mudlib.player import Player, players
|
from mudlib.player import Player, players
|
||||||
from mudlib.render.ansi import RESET, colorize_terrain
|
from mudlib.render.ansi import RESET, colorize_terrain
|
||||||
|
|
@ -89,4 +89,4 @@ async def cmd_look(player: Player, args: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
# Register the look command with its alias
|
# Register the look command with its alias
|
||||||
register("look", cmd_look, aliases=["l"])
|
register(CommandDefinition("look", cmd_look, aliases=["l"]))
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from mudlib.commands import register
|
from mudlib.commands import CommandDefinition, register
|
||||||
from mudlib.player import Player, players
|
from mudlib.player import Player, players
|
||||||
|
|
||||||
# World instance will be injected by the server
|
# World instance will be injected by the server
|
||||||
|
|
@ -154,11 +154,11 @@ async def move_southwest(player: Player, args: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
# Register all movement commands with their aliases
|
# Register all movement commands with their aliases
|
||||||
register("north", move_north, aliases=["n"])
|
register(CommandDefinition("north", move_north, aliases=["n"]))
|
||||||
register("south", move_south, aliases=["s"])
|
register(CommandDefinition("south", move_south, aliases=["s"]))
|
||||||
register("east", move_east, aliases=["e"])
|
register(CommandDefinition("east", move_east, aliases=["e"]))
|
||||||
register("west", move_west, aliases=["w"])
|
register(CommandDefinition("west", move_west, aliases=["w"]))
|
||||||
register("northeast", move_northeast, aliases=["ne"])
|
register(CommandDefinition("northeast", move_northeast, aliases=["ne"]))
|
||||||
register("northwest", move_northwest, aliases=["nw"])
|
register(CommandDefinition("northwest", move_northwest, aliases=["nw"]))
|
||||||
register("southeast", move_southeast, aliases=["se"])
|
register(CommandDefinition("southeast", move_southeast, aliases=["se"]))
|
||||||
register("southwest", move_southwest, aliases=["sw"])
|
register(CommandDefinition("southwest", move_southwest, aliases=["sw"]))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Quit command for disconnecting from the server."""
|
"""Quit command for disconnecting from the server."""
|
||||||
|
|
||||||
from mudlib.commands import register
|
from mudlib.commands import CommandDefinition, register
|
||||||
from mudlib.player import Player, players
|
from mudlib.player import Player, players
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,4 +21,4 @@ async def cmd_quit(player: Player, args: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
# Register the quit command with its aliases
|
# Register the quit command with its aliases
|
||||||
register("quit", cmd_quit, aliases=["q"])
|
register(CommandDefinition("quit", cmd_quit, aliases=["q"], mode="*"))
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, MagicMock
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mudlib import commands
|
from mudlib import commands
|
||||||
from mudlib.commands import look, movement
|
from mudlib.commands import CommandDefinition, look, movement
|
||||||
from mudlib.effects import active_effects, add_effect
|
from mudlib.effects import active_effects, add_effect
|
||||||
from mudlib.player import Player
|
from mudlib.player import Player
|
||||||
from mudlib.render.ansi import RESET
|
from mudlib.render.ansi import RESET
|
||||||
|
|
@ -49,8 +49,9 @@ def test_register_command():
|
||||||
async def test_handler(player, args):
|
async def test_handler(player, args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
commands.register("test", test_handler)
|
commands.register(CommandDefinition("test", test_handler))
|
||||||
assert "test" in commands._registry
|
assert "test" in commands._registry
|
||||||
|
assert commands._registry["test"].handler is test_handler
|
||||||
|
|
||||||
|
|
||||||
def test_register_command_with_aliases():
|
def test_register_command_with_aliases():
|
||||||
|
|
@ -59,12 +60,12 @@ def test_register_command_with_aliases():
|
||||||
async def test_handler(player, args):
|
async def test_handler(player, args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
commands.register("testcmd", test_handler, aliases=["tc", "t"])
|
commands.register(CommandDefinition("testcmd", test_handler, aliases=["tc", "t"]))
|
||||||
assert "testcmd" in commands._registry
|
assert "testcmd" in commands._registry
|
||||||
assert "tc" in commands._registry
|
assert "tc" in commands._registry
|
||||||
assert "t" in commands._registry
|
assert "t" in commands._registry
|
||||||
assert commands._registry["testcmd"] == commands._registry["tc"]
|
assert commands._registry["testcmd"] is commands._registry["tc"]
|
||||||
assert commands._registry["testcmd"] == commands._registry["t"]
|
assert commands._registry["testcmd"] is commands._registry["t"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
@ -78,7 +79,7 @@ async def test_dispatch_routes_to_handler(player):
|
||||||
called = True
|
called = True
|
||||||
received_args = args
|
received_args = args
|
||||||
|
|
||||||
commands.register("testcmd", test_handler)
|
commands.register(CommandDefinition("testcmd", test_handler))
|
||||||
await commands.dispatch(player, "testcmd arg1 arg2")
|
await commands.dispatch(player, "testcmd arg1 arg2")
|
||||||
|
|
||||||
assert called
|
assert called
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue