pub fn format_squash(message: Option<&str>, task_title: &str, id: &str) -> String {
let raw = message.map(str::trim).filter(|s| !s.is_empty()).unwrap_or(task_title);
let (title, body) = split_title_body(raw);
let tagged = format!("{} [{}]", title.trim_end(), id);
match body {
Some(b) if !b.is_empty() => format!("{tagged}\n\n{b}"),
_ => tagged,
}
}
fn split_title_body(s: &str) -> (&str, Option<String>) {
let s = s.trim_end_matches('\n');
let Some(nl) = s.find('\n') else {
return (s, None);
};
let title = &s[..nl];
let body = s[nl + 1..].trim_start_matches('\n').to_string();
(title, Some(body))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn none_uses_task_title() {
assert_eq!(
format_squash(None, "Add feature", "bl-abcd"),
"Add feature [bl-abcd]"
);
}
#[test]
fn single_line_message_is_title_only() {
assert_eq!(
format_squash(Some("Short title"), "ignored", "bl-1234"),
"Short title [bl-1234]"
);
}
#[test]
fn title_and_body_split_on_first_newline() {
let out = format_squash(
Some("Short title\nExplanation paragraph."),
"ignored",
"bl-1234",
);
assert_eq!(out, "Short title [bl-1234]\n\nExplanation paragraph.");
}
#[test]
fn title_and_body_with_existing_blank_line_preserved() {
let out = format_squash(
Some("Short title\n\nExplanation paragraph."),
"ignored",
"bl-1234",
);
assert_eq!(out, "Short title [bl-1234]\n\nExplanation paragraph.");
}
#[test]
fn multi_paragraph_body_is_preserved() {
let out = format_squash(
Some("Short title\n\nFirst paragraph.\n\nSecond paragraph."),
"ignored",
"bl-1234",
);
assert_eq!(
out,
"Short title [bl-1234]\n\nFirst paragraph.\n\nSecond paragraph."
);
}
#[test]
fn trailing_newlines_stripped() {
let out = format_squash(Some("Short title\n\n"), "ignored", "bl-1234");
assert_eq!(out, "Short title [bl-1234]");
}
#[test]
fn empty_message_falls_back_to_task_title() {
assert_eq!(
format_squash(Some(""), "Fallback title", "bl-abcd"),
"Fallback title [bl-abcd]"
);
assert_eq!(
format_squash(Some(" "), "Fallback title", "bl-abcd"),
"Fallback title [bl-abcd]"
);
}
}