1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#[derive(Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Commit<'c> {
    pub raw_subject: &'c str,
    pub body: Option<&'c str>,
}

impl<'c> Commit<'c> {
    pub fn parse(commit: &'c str) -> Result<Self, anyhow::Error> {
        let (raw_subject, body) = split_parts(commit);
        let c = Commit { raw_subject, body };

        Ok(c)
    }
}

impl<'c> crate::style::Style for Commit<'c> {
    fn subject(&self) -> &str {
        self.raw_subject
    }

    fn body(&self) -> Option<&str> {
        self.body
    }

    fn type_(&self) -> Option<unicase::UniCase<&str>> {
        None
    }

    fn scope(&self) -> Option<unicase::UniCase<&str>> {
        None
    }
}

static SECTION_RE: once_cell::sync::Lazy<regex::Regex> =
    once_cell::sync::Lazy::new(|| regex::Regex::new("\r?\n").unwrap());

fn split_parts(commit: &str) -> (&str, Option<&str>) {
    let mut sections = SECTION_RE.splitn(commit, 2);
    let raw_subject = sections.next().expect("Regex should always match");
    let body = sections.next().map(|s| s.trim()).unwrap_or("");
    if body.is_empty() {
        (raw_subject, None)
    } else {
        (raw_subject, Some(body))
    }
}

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

    #[test]
    fn subject() {
        let actual = split_parts("feat(parser): Parse bad greetings");
        let expected = ("feat(parser): Parse bad greetings", None);
        assert_eq!(actual, expected);
    }

    #[test]
    fn body() {
        let actual = split_parts(
            r#"feat(parser): Parse bad greetings

Hello
World

Foo
Bar"#,
        );
        let expected = (
            "feat(parser): Parse bad greetings",
            Some("Hello\nWorld\n\nFoo\nBar"),
        );
        assert_eq!(actual, expected);
    }
}