#[derive(Debug, Clone, PartialEq)]
pub struct CommitMessage {
pub subject: String,
pub body_lines: Vec<String>,
pub comments: Vec<String>,
}
impl CommitMessage {
pub fn parse(input: &str) -> Self {
let all_lines: Vec<&str> = input.lines().collect();
let comment_start = all_lines.iter().position(|line| line.starts_with('#'));
let (message_lines, comment_lines) = if let Some(idx) = comment_start {
(all_lines[..idx].to_vec(), all_lines[idx..].to_vec())
} else {
(all_lines, vec![])
};
let comments: Vec<String> = comment_lines.iter().map(|s| s.to_string()).collect();
if message_lines.is_empty() {
return CommitMessage {
subject: String::new(),
body_lines: Vec::new(),
comments,
};
}
let subject = message_lines[0].to_string();
let mut body_lines = Vec::new();
let mut found_body_start = false;
for line in message_lines.iter().skip(1) {
if !found_body_start {
if line.trim().is_empty() {
continue;
}
found_body_start = true;
}
body_lines.push(line.to_string());
}
CommitMessage {
subject,
body_lines,
comments,
}
}
pub fn to_string(&self) -> String {
let mut result = self.subject.clone();
if !self.body_lines.is_empty() {
result.push_str("\n\n");
result.push_str(&self.body_lines.join("\n"));
}
if !self.comments.is_empty() {
if !result.is_empty() && !result.ends_with('\n') {
result.push('\n');
}
result.push('\n');
result.push_str(&self.comments.join("\n"));
}
if !result.ends_with('\n') {
result.push('\n');
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_message() {
let input = "Fix bug in parser";
let msg = CommitMessage::parse(input);
assert_eq!(msg.subject, "Fix bug in parser");
assert_eq!(msg.body_lines.len(), 0);
assert_eq!(msg.comments.len(), 0);
}
#[test]
fn test_parse_message_with_body() {
let input = "Fix bug in parser\n\nThis fixes the issue with parsing.";
let msg = CommitMessage::parse(input);
assert_eq!(msg.subject, "Fix bug in parser");
assert_eq!(msg.body_lines, vec!["This fixes the issue with parsing."]);
}
#[test]
fn test_parse_message_with_comments() {
let input = "Fix bug\n\nDetails\n# Please enter the commit message";
let msg = CommitMessage::parse(input);
assert_eq!(msg.subject, "Fix bug");
assert_eq!(msg.body_lines, vec!["Details"]);
assert_eq!(msg.comments, vec!["# Please enter the commit message"]);
}
#[test]
fn test_to_string_simple() {
let msg = CommitMessage {
subject: "Fix bug".to_string(),
body_lines: Vec::new(),
comments: Vec::new(),
};
assert_eq!(msg.to_string(), "Fix bug\n");
}
#[test]
fn test_to_string_with_body() {
let msg = CommitMessage {
subject: "Fix bug".to_string(),
body_lines: vec!["Details here".to_string()],
comments: Vec::new(),
};
assert_eq!(msg.to_string(), "Fix bug\n\nDetails here\n");
}
#[test]
fn test_to_string_with_comments() {
let msg = CommitMessage {
subject: "Fix bug".to_string(),
body_lines: vec!["Details".to_string()],
comments: vec!["# Comment".to_string()],
};
assert_eq!(msg.to_string(), "Fix bug\n\nDetails\n\n# Comment\n");
}
}