diff --git a/src/generator/message.rs b/src/generator/message.rs new file mode 100644 index 0000000..0552556 --- /dev/null +++ b/src/generator/message.rs @@ -0,0 +1,177 @@ +use crate::parser::conventional::ConventionalMessage; + +#[derive(Debug, Clone)] +pub struct MessageGenerator { + r#type: String, + scope: Option, + description: String, + body: Option, + breaking: bool, + breaking_description: Option, + footer: Option, +} + +impl From for MessageGenerator { + fn from(msg: ConventionalMessage) -> Self { + MessageGenerator { + r#type: msg.r#type, + scope: msg.scope, + description: msg.description, + body: msg.body, + breaking: msg.breaking, + breaking_description: msg.breaking_description, + footer: msg.footer, + } + } +} + +impl MessageGenerator { + pub fn new() -> Self { + MessageGenerator { + r#type: "chore".to_string(), + scope: None, + description: String::new(), + body: None, + breaking: false, + breaking_description: None, + footer: None, + } + } + + pub fn set_type(&mut self, r#type: String) { + self.r#type = r#type; + } + + pub fn set_scope(&mut self, scope: Option) { + self.scope = scope; + } + + pub fn set_scope_opt(&mut self, scope: Option) { + self.scope = scope; + } + + pub fn clear_scope(&mut self) { + self.scope = None; + } + + pub fn set_description(&mut self, description: String) { + self.description = description; + } + + pub fn set_body(&mut self, body: String) { + self.body = Some(body); + } + + pub fn set_body_opt(&mut self, body: Option) { + self.body = body; + } + + pub fn set_breaking(&mut self, breaking: bool) { + self.breaking = breaking; + } + + pub fn set_breaking_description(&mut self, description: String) { + self.breaking_description = Some(description); + } + + pub fn set_footer(&mut self, footer: String) { + self.footer = Some(footer); + } + + pub fn get_type(&self) -> &str { + &self.r#type + } + + pub fn get_scope(&self) -> Option<&str> { + self.scope.as_deref() + } + + pub fn get_description(&self) -> &str { + &self.description + } + + pub fn get_body(&self) -> Option<&str> { + self.body.as_deref() + } + + pub fn is_breaking(&self) -> bool { + self.breaking + } + + pub fn format_message(&self) -> String { + let mut message = Vec::new(); + + let scope_part = self.scope.as_ref() + .map(|s| format!("({})", s)) + .unwrap_or_default(); + + let breaking_marker = if self.breaking { "!" } else { "" }; + + let description = if self.description.trim().is_empty() { + "no description".to_string() + } else { + self.description.clone() + }; + + message.push(format!( + "{}{}{}: {}", + self.r#type, + scope_part, + breaking_marker, + description + )); + + if let Some(ref body) = self.body { + if !body.trim().is_empty() { + message.push(String::new()); + message.push(wrap_text(body, 72)); + } + } + + if self.breaking { + message.push(String::new()); + if let Some(ref desc) = self.breaking_description { + message.push(format!("BREAKING CHANGE: {}", wrap_text(desc, 72))); + } else { + message.push("BREAKING CHANGE:".to_string()); + } + } + + if let Some(ref footer) = self.footer { + message.push(String::new()); + message.push(footer.clone()); + } + + message.join("\n") + } +} + +fn wrap_text(text: &str, width: usize) -> String { + let mut result = String::new(); + let mut current_line = String::new(); + + for word in text.split_whitespace() { + if current_line.is_empty() { + current_line = word.to_string(); + } else if current_line.len() + word.len() + 1 <= width { + current_line.push(' '); + current_line.push_str(word); + } else { + result.push_str(¤t_line); + result.push('\n'); + current_line = word.to_string(); + } + } + + if !current_line.is_empty() { + result.push_str(¤t_line); + } + + result +} + +impl Default for MessageGenerator { + fn default() -> Self { + MessageGenerator::new() + } +}