mit_commit/
scissors.rs

1use std::borrow::Cow;
2
3use crate::Comment;
4
5const SCISSORS_MARKER: &str = "------------------------ >8 ------------------------";
6
7/// The [`Scissors`] from a [`CommitMessage`]
8#[derive(Debug, PartialEq, Eq, Clone)]
9pub struct Scissors<'a> {
10    scissors: Cow<'a, str>,
11}
12
13impl<'a> Scissors<'a> {
14    pub(crate) fn guess_comment_character(message: &str) -> Option<char> {
15        Self::guess_comment_char_from_scissors(message)
16            .or_else(|| Self::guess_comment_char_from_last_possibility(message))
17    }
18
19    fn guess_comment_char_from_last_possibility(message: &str) -> Option<char> {
20        message
21            .lines()
22            .filter_map(|line| {
23                line.chars()
24                    .next()
25                    .filter(|first_letter| Comment::is_legal_comment_char(*first_letter))
26            })
27            .last()
28    }
29
30    fn guess_comment_char_from_scissors(message: &str) -> Option<char> {
31        message
32            .lines()
33            .filter_map(|line| {
34                let mut line_chars = line.chars();
35                let first_character = line_chars.next();
36                first_character.filter(|cc| Comment::is_legal_comment_char(*cc))?;
37                line_chars.next().filter(|cc| *cc == ' ')?;
38
39                if SCISSORS_MARKER != line_chars.as_str() {
40                    return None;
41                }
42
43                first_character
44            })
45            .last()
46    }
47
48    pub(crate) fn parse_sections(message: &str) -> (Cow<'a, str>, Option<Self>) {
49        message
50            .lines()
51            .position(|line| line.ends_with(SCISSORS_MARKER))
52            .map_or_else(
53                || (message.to_string().into(), None),
54                |scissors_position| {
55                    let lines = message.lines().collect::<Vec<_>>();
56                    let body = lines
57                        .clone()
58                        .into_iter()
59                        .take(scissors_position)
60                        .collect::<Vec<_>>()
61                        .join("\n");
62                    let scissors_string = &lines
63                        .into_iter()
64                        .skip(scissors_position)
65                        .collect::<Vec<_>>()
66                        .join("\n");
67
68                    let scissors = if message.ends_with('\n') {
69                        Self::from(format!("{scissors_string}\n"))
70                    } else {
71                        Self::from(scissors_string.clone())
72                    };
73
74                    (body.into(), Some(scissors))
75                },
76            )
77    }
78}
79
80impl<'a> From<Cow<'a, str>> for Scissors<'a> {
81    fn from(scissors: Cow<'a, str>) -> Self {
82        Self { scissors }
83    }
84}
85
86impl<'a> From<&'a str> for Scissors<'a> {
87    fn from(scissors: &'a str) -> Self {
88        Self {
89            scissors: scissors.into(),
90        }
91    }
92}
93
94impl<'a> From<String> for Scissors<'a> {
95    fn from(scissors: String) -> Self {
96        Self {
97            scissors: scissors.into(),
98        }
99    }
100}
101
102impl<'a> From<Scissors<'a>> for String {
103    fn from(scissors: Scissors<'a>) -> Self {
104        scissors.scissors.into()
105    }
106}