From 14b6ef81bddd09c0156c19529129e8ab4fd039c2 Mon Sep 17 00:00:00 2001 From: Gustavo Nobrega Date: Sat, 18 Apr 2026 11:52:31 -0700 Subject: [PATCH] Add HelpTopic inventory type for non-command help entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a compile-time registry for free-form help topics that are not tied to a command handler — channel modes, user modes, or any other concept a user might query with HELP . Topics are registered at the call site via inventory::submit!: inventory::submit!(HelpTopic { topic: "CMODE_SECRET", lines: &[ "CMODE_SECRET (+s)", "", "Marks the channel as secret.", ], }); CommandDispatcher gains two new methods: - get_help_topic(topic) -> Option<&'static [&'static str]> - iter_help_topics() -> impl Iterator ClientServer forwards both so command handlers can call them directly. A HELP handler (see #129) should query the command registry for command docs and fall back to get_help_topic() for concept entries, giving full coverage with no runtime I/O. --- sable_ircd/src/command/dispatcher.rs | 43 ++++++++++++++++++++++++++++ sable_ircd/src/server/mod.rs | 14 +++++++++ 2 files changed, 57 insertions(+) diff --git a/sable_ircd/src/command/dispatcher.rs b/sable_ircd/src/command/dispatcher.rs index 67b0bc5e..71ef87b2 100644 --- a/sable_ircd/src/command/dispatcher.rs +++ b/sable_ircd/src/command/dispatcher.rs @@ -14,6 +14,33 @@ pub struct CommandRegistration { pub(super) handler: CommandHandlerWrapper, } +/// A free-form help topic not tied to a command handler. +/// +/// Use this for topics that describe concepts rather than commands — for example +/// channel modes, user modes, or any other subject a user might query with `HELP`. +/// +/// Register a topic at the call site with [`inventory::submit!`]: +/// +/// ```rust,ignore +/// inventory::submit!(HelpTopic { +/// topic: "CMODE_SECRET", +/// lines: &[ +/// "CMODE_SECRET (+s)", +/// "", +/// "Marks the channel as secret. Secret channels are hidden from /LIST", +/// "and /WHOIS for users who are not members.", +/// ], +/// }); +/// ``` +/// +/// Topics are looked up case-insensitively via [`CommandDispatcher::get_help_topic`]. +pub struct HelpTopic { + /// The name of the topic, matched case-insensitively against the argument to `HELP`. + pub topic: &'static str, + /// Lines of help text returned to the client. + pub lines: &'static [&'static str], +} + /// A command dispatcher. Collects registered command handlers and allows lookup by /// command name. pub struct CommandDispatcher { @@ -21,6 +48,7 @@ pub struct CommandDispatcher { } inventory::collect!(CommandRegistration); +inventory::collect!(HelpTopic); impl CommandDispatcher { /// Construct a default `CommandDispatcher`. @@ -67,4 +95,19 @@ impl CommandDispatcher { } } } + + /// Look up a free-form [`HelpTopic`] by name (case-insensitive). + /// + /// Returns the topic's lines of text, or `None` if no topic with that name was registered. + pub fn get_help_topic(&self, topic: &str) -> Option<&'static [&'static str]> { + inventory::iter:: + .into_iter() + .find(|t| t.topic.eq_ignore_ascii_case(topic)) + .map(|t| t.lines) + } + + /// Iterate over all registered free-form [`HelpTopic`] entries. + pub fn iter_help_topics(&self) -> impl Iterator { + inventory::iter::.into_iter() + } } diff --git a/sable_ircd/src/server/mod.rs b/sable_ircd/src/server/mod.rs index 5d48a62c..ede9e179 100644 --- a/sable_ircd/src/server/mod.rs +++ b/sable_ircd/src/server/mod.rs @@ -143,6 +143,20 @@ impl ClientServer { &self.client_caps } + /// Look up a free-form help topic by name (case-insensitive). + /// + /// Returns the help lines registered via [`command::HelpTopic`], or `None` if no matching + /// topic was found. Command handlers that implement `HELP` should query this alongside + /// the command registry to cover both command docs and concept topics (modes, etc.). + pub fn get_help_topic(&self, topic: &str) -> Option<&'static [&'static str]> { + self.command_dispatcher.get_help_topic(topic) + } + + /// Iterate over all free-form help topics registered via [`command::HelpTopic`]. + pub fn iter_help_topics(&self) -> impl Iterator { + self.command_dispatcher.iter_help_topics() + } + /// Store a [`MessageSink`] to use when processing updates caused by the given event pub(crate) fn store_response_sink( &self,