commitfmt 0.0.1

A git commit message formatter
Documentation
use crate::parser::CommitMessage;

pub fn wrap_body_at_length(msg: &mut CommitMessage, width: usize) {
    let mut wrapped_lines = Vec::new();

    for line in &msg.body_lines {
        if line.trim().is_empty() {
            wrapped_lines.push(String::new());
        } else if line.len() > width {
            wrapped_lines.extend(wrap_line(line, width));
        } else {
            wrapped_lines.push(line.clone());
        }
    }

    msg.body_lines = wrapped_lines;
}

fn wrap_line(line: &str, width: usize) -> Vec<String> {
    let words: Vec<&str> = line.split_whitespace().collect();
    let mut result = Vec::new();
    let mut current_line = String::new();

    for word in words {
        if word.len() > width {
            if !current_line.is_empty() {
                result.push(current_line.clone());
                current_line.clear();
            }
            result.push(word.to_string());
            continue;
        }

        let test_line = if current_line.is_empty() {
            word.to_string()
        } else {
            format!("{} {}", current_line, word)
        };

        if test_line.len() <= width {
            current_line = test_line;
        } else {
            result.push(current_line.clone());
            current_line = word.to_string();
        }
    }

    if !current_line.is_empty() {
        result.push(current_line);
    }

    if result.is_empty() {
        result.push(String::new());
    }

    result
}

pub fn enforce_subject_length(msg: &mut CommitMessage, max_length: usize) {
    if msg.subject.len() > max_length {
        let subject_text = msg.subject.clone();
        let words: Vec<&str> = subject_text.split_whitespace().collect();
        let mut subject_words = Vec::new();
        let mut overflow_words = Vec::new();
        let mut current_len = 0;
        let mut found_split = false;

        for (i, word) in words.iter().enumerate() {
            let word_len = word.len();
            let space_len = if i > 0 { 1 } else { 0 };

            if !found_split && current_len + space_len + word_len <= max_length {
                subject_words.push(*word);
                current_len += space_len + word_len;
            } else {
                found_split = true;
                overflow_words.push(*word);
            }
        }

        if !overflow_words.is_empty() {
            msg.subject = subject_words.join(" ");
            msg.body_lines.insert(0, overflow_words.join(" "));
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_wrap_line() {
        let line = "This is a very long line that should be wrapped at seventy two characters to follow best practices";
        let wrapped = wrap_line(line, 72);
        assert!(wrapped.iter().all(|l| l.len() <= 72));
    }
}