Files
huskies/server/src/chat/transport/matrix/bot/format.rs
T
daveandClaude Opus 4.6 845b85e7a7 fix: add --all to cargo fmt in script/test and autoformat codebase
cargo fmt without --all fails with "Failed to find targets" in
workspace repos. This was blocking every story's gates. Also ran
cargo fmt --all to fix all existing formatting issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:07:08 +00:00

124 lines
4.2 KiB
Rust

//! Matrix message formatting — Markdown-to-HTML conversion and startup announcements.
use pulldown_cmark::{Options, Parser, html};
/// Format the startup greeting the bot sends to each room when it comes online.
///
/// Uses the bot's configured display name so the message reads naturally
/// (e.g. "Timmy is online.").
pub fn format_startup_announcement(bot_name: &str) -> String {
format!("{bot_name} is online.")
}
/// Convert a Markdown string to an HTML string using pulldown-cmark.
///
/// Enables the standard extension set (tables, footnotes, strikethrough,
/// tasklists) so that common Markdown constructs render correctly in Matrix
/// clients such as Element.
pub fn markdown_to_html(markdown: &str) -> String {
let normalized = crate::chat::util::normalize_line_breaks(markdown);
let options = Options::ENABLE_TABLES
| Options::ENABLE_FOOTNOTES
| Options::ENABLE_STRIKETHROUGH
| Options::ENABLE_TASKLISTS;
let parser = Parser::new_ext(&normalized, options);
let mut html_output = String::new();
html::push_html(&mut html_output, parser);
html_output
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn markdown_to_html_bold() {
let html = markdown_to_html("**bold**");
assert!(
html.contains("<strong>bold</strong>"),
"expected <strong>: {html}"
);
}
#[test]
fn markdown_to_html_unordered_list() {
let html = markdown_to_html("- item one\n- item two");
assert!(html.contains("<ul>"), "expected <ul>: {html}");
assert!(
html.contains("<li>item one</li>"),
"expected list item: {html}"
);
}
#[test]
fn markdown_to_html_inline_code() {
let html = markdown_to_html("`inline_code()`");
assert!(
html.contains("<code>inline_code()</code>"),
"expected <code>: {html}"
);
}
#[test]
fn markdown_to_html_code_block() {
let html = markdown_to_html("```rust\nfn main() {}\n```");
assert!(html.contains("<pre>"), "expected <pre>: {html}");
assert!(html.contains("<code"), "expected <code> inside pre: {html}");
assert!(
html.contains("fn main() {}"),
"expected code content: {html}"
);
}
#[test]
fn markdown_to_html_plain_text_passthrough() {
let html = markdown_to_html("Hello, world!");
assert!(
html.contains("Hello, world!"),
"expected plain text passthrough: {html}"
);
}
#[test]
fn markdown_to_html_single_newline_prose_becomes_paragraphs() {
// Single newlines between prose sentences should produce separate paragraphs.
let html = markdown_to_html("Sentence one.\nSentence two.");
assert!(
html.contains("<p>Sentence one.</p>"),
"expected separate paragraph for first sentence: {html}"
);
assert!(
html.contains("<p>Sentence two.</p>"),
"expected separate paragraph for second sentence: {html}"
);
}
#[test]
fn startup_announcement_uses_bot_name() {
assert_eq!(format_startup_announcement("Timmy"), "Timmy is online.");
}
#[test]
fn startup_announcement_uses_configured_display_name_not_hardcoded() {
assert_eq!(format_startup_announcement("HAL"), "HAL is online.");
assert_eq!(
format_startup_announcement("Assistant"),
"Assistant is online."
);
}
#[test]
fn bot_name_defaults_to_assistant_when_display_name_absent() {
// When display_name is not set in bot.toml, bot_name should be "Assistant".
// This mirrors the logic in run_bot: config.display_name.clone().unwrap_or_else(...)
fn resolve_bot_name(display_name: Option<String>) -> String {
display_name.unwrap_or_else(|| "Assistant".to_string())
}
assert_eq!(resolve_bot_name(None), "Assistant");
assert_eq!(resolve_bot_name(Some("Timmy".to_string())), "Timmy");
}
}