From d7698ca830a01ae0084cdb1db356d05819150ec3 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Sun, 15 Feb 2026 09:48:26 -0500 Subject: [PATCH] Wire TOML help topics into help command --- src/mudlib/commands/help.py | 12 +++++++ src/mudlib/server.py | 10 +++++- tests/test_help_topics.py | 69 ++++++++++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/mudlib/commands/help.py b/src/mudlib/commands/help.py index e425dbe..9b08534 100644 --- a/src/mudlib/commands/help.py +++ b/src/mudlib/commands/help.py @@ -4,11 +4,14 @@ from typing import TYPE_CHECKING, cast from mudlib.commands import CommandDefinition, _registry, register, resolve_prefix from mudlib.commands.movement import DIRECTIONS +from mudlib.content import HelpTopic from mudlib.player import Player if TYPE_CHECKING: from mudlib.combat.moves import CombatMove +_help_topics: dict[str, HelpTopic] = {} + async def _show_command_detail(player: Player, command_name: str) -> None: """Show detailed information about a specific command. @@ -438,6 +441,15 @@ async def cmd_help(player: Player, args: str) -> None: ) return + # Check TOML help topics first + topic = _help_topics.get(args) + if topic is not None: + if topic.admin and not player.is_admin: + await player.send(f"Unknown command: {args}\r\n") + return + await player.send(f"{topic.title}\r\n{topic.body}\r\n") + return + # Check if this is a help topic (hidden command) defn = _registry.get(args) if defn is not None and defn.hidden: diff --git a/src/mudlib/server.py b/src/mudlib/server.py index efd9237..b216001 100644 --- a/src/mudlib/server.py +++ b/src/mudlib/server.py @@ -42,7 +42,8 @@ import mudlib.commands.use from mudlib.caps import parse_mtts from mudlib.combat.commands import register_combat_commands from mudlib.combat.engine import process_combat -from mudlib.content import load_commands +from mudlib.commands.help import _help_topics +from mudlib.content import load_commands, load_help_topics from mudlib.corpse import process_decomposing from mudlib.crafting import load_recipes, recipes from mudlib.creation import character_creation @@ -598,6 +599,13 @@ async def run_server() -> None: mudlib.commands.register(cmd_def) log.debug("registered content command: %s", cmd_def.name) + # Load help topics from content/help/ + help_dir = pathlib.Path(__file__).resolve().parents[2] / "content" / "help" + if help_dir.exists(): + loaded_topics = load_help_topics(help_dir) + _help_topics.update(loaded_topics) + log.info("loaded %d help topics from %s", len(loaded_topics), help_dir) + # Load combat moves and register as commands combat_dir = pathlib.Path(__file__).resolve().parents[2] / "content" / "combat" if combat_dir.exists(): diff --git a/tests/test_help_topics.py b/tests/test_help_topics.py index b939cce..af89eed 100644 --- a/tests/test_help_topics.py +++ b/tests/test_help_topics.py @@ -1,10 +1,14 @@ """Tests for TOML help topic loading.""" import textwrap +from unittest.mock import AsyncMock, MagicMock import pytest -from mudlib.content import load_help_topics +from mudlib import commands +from mudlib.commands import help as help_mod # noqa: F401 +from mudlib.commands.help import _help_topics +from mudlib.content import HelpTopic, load_help_topics @pytest.fixture @@ -77,3 +81,66 @@ def test_load_help_topics_skips_bad_files(tmp_path): topics = load_help_topics(tmp_path) assert "good" in topics assert "broken" not in topics + + +@pytest.fixture +def mock_writer(): + writer = MagicMock() + writer.write = MagicMock() + writer.drain = AsyncMock() + return writer + + +@pytest.fixture +def player(mock_writer): + from mudlib.player import Player + + return Player(name="Tester", x=0, y=0, reader=MagicMock(), writer=mock_writer) + + +@pytest.fixture +def admin_player(mock_writer): + from mudlib.player import Player + + p = Player(name="Admin", x=0, y=0, reader=MagicMock(), writer=mock_writer) + p.is_admin = True + return p + + +@pytest.fixture(autouse=True) +def _clear_topics(): + _help_topics.clear() + yield + _help_topics.clear() + + +@pytest.mark.asyncio +async def test_help_shows_toml_topic(player): + _help_topics["combat"] = HelpTopic( + name="combat", body="fight stuff", title="combat primer" + ) + await commands.dispatch(player, "help combat") + output = "".join(c[0][0] for c in player.writer.write.call_args_list) + assert "combat primer" in output + assert "fight stuff" in output + + +@pytest.mark.asyncio +async def test_help_admin_topic_hidden_from_players(player): + _help_topics["secret"] = HelpTopic( + name="secret", body="hidden", title="secret stuff", admin=True + ) + await commands.dispatch(player, "help secret") + output = "".join(c[0][0] for c in player.writer.write.call_args_list) + assert "hidden" not in output + assert "unknown" in output.lower() + + +@pytest.mark.asyncio +async def test_help_admin_topic_visible_to_admins(admin_player): + _help_topics["secret"] = HelpTopic( + name="secret", body="hidden", title="secret stuff", admin=True + ) + await commands.dispatch(admin_player, "help secret") + output = "".join(c[0][0] for c in admin_player.writer.write.call_args_list) + assert "hidden" in output