From ecfa7cea394ad2b896c0d583a7fd1020cdc177a0 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 23 Feb 2026 23:12:10 +0100 Subject: [PATCH] fix: indent docstring body in help paste output Command name stays flush-left, docstring lines indented 4 spaces. Plugin descriptions in the full reference also indented under headers. Adds test_help_paste_body_indented to verify formatting. Co-Authored-By: Claude Opus 4.6 --- plugins/core.py | 12 ++++++++---- tests/test_core.py | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/plugins/core.py b/plugins/core.py index 337c489..df08981 100644 --- a/plugins/core.py +++ b/plugins/core.py @@ -9,14 +9,18 @@ from derp.plugin import command def _build_cmd_detail(handler, prefix: str) -> str: - """Extract and format a command's docstring into a detail block.""" + """Extract and format a command's docstring into a detail block. + + Command name anchors the left edge; docstring body is indented 4 spaces. + """ doc = textwrap.dedent(handler.callback.__doc__ or "").strip() if not doc: return "" header = f"{prefix}{handler.name}" if handler.help: header += f" -- {handler.help}" - return f"{header}\n{doc}" + indented = textwrap.indent(doc, " ") + return f"{header}\n{indented}" async def _paste(bot, text: str) -> str | None: @@ -108,7 +112,7 @@ async def cmd_help(bot, message): desc = (getattr(mod, "__doc__", "") or "").split("\n")[0].strip() if mod else "" header = f"[{plugin_name}]" if desc: - header += f" {desc}" + header += f"\n {desc}" cmd_lines = [] for cmd_name in plugins[plugin_name]: h = bot.registry.commands[cmd_name] @@ -120,7 +124,7 @@ async def cmd_help(bot, message): if h.help: line += f" -- {h.help}" cmd_lines.append(line) - blocks.append(header + "\n" + "\n\n".join(cmd_lines)) + blocks.append(header + "\n\n" + "\n\n".join(cmd_lines)) if blocks: url = await _paste(bot, "\n\n".join(blocks)) if url: diff --git a/tests/test_core.py b/tests/test_core.py index b30ffa9..08108e5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -130,10 +130,19 @@ def _cmd_no_doc(): pass -def _make_fp_module(url="https://paste.example.com/abc/raw"): - """Create a fake flaskpaste module that returns a fixed URL.""" +def _make_fp_module(url="https://paste.example.com/abc/raw", capture=None): + """Create a fake flaskpaste module that returns a fixed URL. + + If capture is a list, appended paste content is stored there. + """ mod = types.ModuleType("flaskpaste") - mod.create_paste = lambda bot, text: url + + def _create(bot, text): + if capture is not None: + capture.append(text) + return url + + mod.create_paste = _create return mod @@ -227,3 +236,23 @@ class TestHelpCommand: assert len(bot.replied) == 1 assert "!widget -- Manage widgets" in bot.replied[0] assert "https://" not in bot.replied[0] + + def test_help_paste_body_indented(self): + """Paste body has command name flush-left, docstring indented.""" + bot = _FakeBot() + pastes: list[str] = [] + bot.registry._modules["flaskpaste"] = _make_fp_module(capture=pastes) + bot.registry.commands["widget"] = _FakeHandler( + name="widget", callback=_cmd_with_doc, + help="Manage widgets", plugin="widgets", + ) + msg = _Msg(text="!help widget") + asyncio.run(_mod.cmd_help(bot, msg)) + assert len(pastes) == 1 + lines = pastes[0].split("\n") + # First line: command header, no indent + assert lines[0] == "!widget -- Manage widgets" + # Docstring lines are indented 4 spaces + for line in lines[1:]: + if line.strip(): + assert line.startswith(" "), f"not indented: {line!r}"