fix: 3-level hierarchy in help paste output
Plugin name at column 0, command at indent 4, docstring at indent 8. Single-command paste keeps command at 0, docstring at 4. Only paste when actual docstring content exists. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,18 +8,20 @@ from derp import __version__
|
|||||||
from derp.plugin import command
|
from derp.plugin import command
|
||||||
|
|
||||||
|
|
||||||
def _build_cmd_detail(handler, prefix: str) -> str:
|
def _build_cmd_detail(handler, prefix: str, indent: int = 0) -> str:
|
||||||
"""Extract and format a command's docstring into a detail block.
|
"""Format command header + docstring at the given indent level.
|
||||||
|
|
||||||
Command name anchors the left edge; docstring body is indented 4 spaces.
|
Command name sits at *indent*, docstring body at *indent + 4*.
|
||||||
|
Returns just the header line when no docstring exists.
|
||||||
"""
|
"""
|
||||||
doc = textwrap.dedent(handler.callback.__doc__ or "").strip()
|
pad = " " * indent
|
||||||
if not doc:
|
header = f"{pad}{prefix}{handler.name}"
|
||||||
return ""
|
|
||||||
header = f"{prefix}{handler.name}"
|
|
||||||
if handler.help:
|
if handler.help:
|
||||||
header += f" -- {handler.help}"
|
header += f" -- {handler.help}"
|
||||||
indented = textwrap.indent(doc, " ")
|
doc = textwrap.dedent(handler.callback.__doc__ or "").strip()
|
||||||
|
if not doc:
|
||||||
|
return header
|
||||||
|
indented = textwrap.indent(doc, " " * (indent + 4))
|
||||||
return f"{header}\n{indented}"
|
return f"{header}\n{indented}"
|
||||||
|
|
||||||
|
|
||||||
@@ -57,8 +59,8 @@ async def cmd_help(bot, message):
|
|||||||
if handler and bot._plugin_allowed(handler.plugin, channel):
|
if handler and bot._plugin_allowed(handler.plugin, channel):
|
||||||
help_text = handler.help or "No help available."
|
help_text = handler.help or "No help available."
|
||||||
reply = f"{bot.prefix}{name} -- {help_text}"
|
reply = f"{bot.prefix}{name} -- {help_text}"
|
||||||
|
if (handler.callback.__doc__ or "").strip():
|
||||||
detail = _build_cmd_detail(handler, bot.prefix)
|
detail = _build_cmd_detail(handler, bot.prefix)
|
||||||
if detail:
|
|
||||||
url = await _paste(bot, detail)
|
url = await _paste(bot, detail)
|
||||||
if url:
|
if url:
|
||||||
reply += f" | {url}"
|
reply += f" | {url}"
|
||||||
@@ -77,15 +79,20 @@ async def cmd_help(bot, message):
|
|||||||
if cmds:
|
if cmds:
|
||||||
lines.append(f"Commands: {', '.join(bot.prefix + c for c in cmds)}")
|
lines.append(f"Commands: {', '.join(bot.prefix + c for c in cmds)}")
|
||||||
reply = " | ".join(lines)
|
reply = " | ".join(lines)
|
||||||
# Build detail block for all plugin commands
|
# Build detail: plugin header + indented commands
|
||||||
blocks = []
|
section_lines = [f"[{name}]"]
|
||||||
|
if desc:
|
||||||
|
section_lines.append(f" {desc}")
|
||||||
|
section_lines.append("")
|
||||||
|
has_detail = False
|
||||||
for cmd_name in cmds:
|
for cmd_name in cmds:
|
||||||
h = bot.registry.commands[cmd_name]
|
h = bot.registry.commands[cmd_name]
|
||||||
blk = _build_cmd_detail(h, bot.prefix)
|
section_lines.append(_build_cmd_detail(h, bot.prefix, indent=4))
|
||||||
if blk:
|
section_lines.append("")
|
||||||
blocks.append(blk)
|
if (h.callback.__doc__ or "").strip():
|
||||||
if blocks:
|
has_detail = True
|
||||||
url = await _paste(bot, "\n\n".join(blocks))
|
if has_detail:
|
||||||
|
url = await _paste(bot, "\n".join(section_lines).rstrip())
|
||||||
if url:
|
if url:
|
||||||
reply += f" | {url}"
|
reply += f" | {url}"
|
||||||
await bot.reply(message, reply)
|
await bot.reply(message, reply)
|
||||||
@@ -106,27 +113,21 @@ async def cmd_help(bot, message):
|
|||||||
for cmd_name in names:
|
for cmd_name in names:
|
||||||
h = bot.registry.commands[cmd_name]
|
h = bot.registry.commands[cmd_name]
|
||||||
plugins.setdefault(h.plugin, []).append(cmd_name)
|
plugins.setdefault(h.plugin, []).append(cmd_name)
|
||||||
blocks = []
|
sections = []
|
||||||
for plugin_name in sorted(plugins):
|
for plugin_name in sorted(plugins):
|
||||||
mod = bot.registry._modules.get(plugin_name)
|
mod = bot.registry._modules.get(plugin_name)
|
||||||
desc = (getattr(mod, "__doc__", "") or "").split("\n")[0].strip() if mod else ""
|
desc = (getattr(mod, "__doc__", "") or "").split("\n")[0].strip() if mod else ""
|
||||||
header = f"[{plugin_name}]"
|
section_lines = [f"[{plugin_name}]"]
|
||||||
if desc:
|
if desc:
|
||||||
header += f"\n {desc}"
|
section_lines.append(f" {desc}")
|
||||||
cmd_lines = []
|
section_lines.append("")
|
||||||
for cmd_name in plugins[plugin_name]:
|
for cmd_name in plugins[plugin_name]:
|
||||||
h = bot.registry.commands[cmd_name]
|
h = bot.registry.commands[cmd_name]
|
||||||
detail = _build_cmd_detail(h, bot.prefix)
|
section_lines.append(_build_cmd_detail(h, bot.prefix, indent=4))
|
||||||
if detail:
|
section_lines.append("")
|
||||||
cmd_lines.append(detail)
|
sections.append("\n".join(section_lines).rstrip())
|
||||||
else:
|
if sections:
|
||||||
line = f"{bot.prefix}{cmd_name}"
|
url = await _paste(bot, "\n\n".join(sections))
|
||||||
if h.help:
|
|
||||||
line += f" -- {h.help}"
|
|
||||||
cmd_lines.append(line)
|
|
||||||
blocks.append(header + "\n\n" + "\n\n".join(cmd_lines))
|
|
||||||
if blocks:
|
|
||||||
url = await _paste(bot, "\n\n".join(blocks))
|
|
||||||
if url:
|
if url:
|
||||||
reply += f" | {url}"
|
reply += f" | {url}"
|
||||||
await bot.reply(message, reply)
|
await bot.reply(message, reply)
|
||||||
|
|||||||
@@ -237,8 +237,8 @@ class TestHelpCommand:
|
|||||||
assert "!widget -- Manage widgets" in bot.replied[0]
|
assert "!widget -- Manage widgets" in bot.replied[0]
|
||||||
assert "https://" not in bot.replied[0]
|
assert "https://" not in bot.replied[0]
|
||||||
|
|
||||||
def test_help_paste_body_indented(self):
|
def test_help_cmd_paste_hierarchy(self):
|
||||||
"""Paste body has command name flush-left, docstring indented."""
|
"""Single-command paste: header at 0, docstring at 4."""
|
||||||
bot = _FakeBot()
|
bot = _FakeBot()
|
||||||
pastes: list[str] = []
|
pastes: list[str] = []
|
||||||
bot.registry._modules["flaskpaste"] = _make_fp_module(capture=pastes)
|
bot.registry._modules["flaskpaste"] = _make_fp_module(capture=pastes)
|
||||||
@@ -250,9 +250,39 @@ class TestHelpCommand:
|
|||||||
asyncio.run(_mod.cmd_help(bot, msg))
|
asyncio.run(_mod.cmd_help(bot, msg))
|
||||||
assert len(pastes) == 1
|
assert len(pastes) == 1
|
||||||
lines = pastes[0].split("\n")
|
lines = pastes[0].split("\n")
|
||||||
# First line: command header, no indent
|
# Level 0: command header flush-left
|
||||||
assert lines[0] == "!widget -- Manage widgets"
|
assert lines[0] == "!widget -- Manage widgets"
|
||||||
# Docstring lines are indented 4 spaces
|
# Level 1: docstring lines indented 4 spaces
|
||||||
for line in lines[1:]:
|
for line in lines[1:]:
|
||||||
if line.strip():
|
if line.strip():
|
||||||
assert line.startswith(" "), f"not indented: {line!r}"
|
assert line.startswith(" "), f"not indented: {line!r}"
|
||||||
|
|
||||||
|
def test_help_list_paste_hierarchy(self):
|
||||||
|
"""Full reference paste: plugin at 0, command at 4, doc at 8."""
|
||||||
|
bot = _FakeBot()
|
||||||
|
pastes: list[str] = []
|
||||||
|
bot.registry._modules["flaskpaste"] = _make_fp_module(capture=pastes)
|
||||||
|
mod = types.ModuleType("core")
|
||||||
|
mod.__doc__ = "Core plugin."
|
||||||
|
bot.registry._modules["core"] = mod
|
||||||
|
bot.registry.commands["state"] = _FakeHandler(
|
||||||
|
name="state", callback=_cmd_with_doc,
|
||||||
|
help="Inspect state", plugin="core",
|
||||||
|
)
|
||||||
|
msg = _Msg(text="!help")
|
||||||
|
asyncio.run(_mod.cmd_help(bot, msg))
|
||||||
|
assert len(pastes) == 1
|
||||||
|
text = pastes[0]
|
||||||
|
lines = text.split("\n")
|
||||||
|
# Level 0: plugin header
|
||||||
|
assert lines[0] == "[core]"
|
||||||
|
# Level 1: plugin description
|
||||||
|
assert lines[1] == " Core plugin."
|
||||||
|
# Blank separator
|
||||||
|
assert lines[2] == ""
|
||||||
|
# Level 1: command header at indent 4
|
||||||
|
assert lines[3] == " !state -- Inspect state"
|
||||||
|
# Level 2: docstring at indent 8
|
||||||
|
for line in lines[4:]:
|
||||||
|
if line.strip():
|
||||||
|
assert line.startswith(" "), f"not at indent 8: {line!r}"
|
||||||
|
|||||||
Reference in New Issue
Block a user