git_mailmap/
parse.rs

1mod error {
2    use bstr::BString;
3    use quick_error::quick_error;
4
5    quick_error! {
6        /// The error returned by [`parse()`][crate::parse()].
7        #[derive(Debug)]
8        #[allow(missing_docs)]
9        pub enum Error {
10            UnconsumedInput { line_number: usize, line: BString } {
11                display("Line {} has too many names or emails, or none at all: {}", line_number, line)
12            }
13            Malformed { line_number: usize, line: BString, message: String } {
14                display("{}: {:?}: {}", line_number, line, message)
15            }
16        }
17    }
18}
19
20use bstr::{BStr, ByteSlice};
21pub use error::Error;
22
23use crate::Entry;
24
25/// An iterator to parse mailmap lines on-demand.
26pub struct Lines<'a> {
27    lines: bstr::Lines<'a>,
28    line_no: usize,
29}
30
31impl<'a> Lines<'a> {
32    pub(crate) fn new(input: &'a [u8]) -> Self {
33        Lines {
34            lines: input.as_bstr().lines(),
35            line_no: 0,
36        }
37    }
38}
39
40impl<'a> Iterator for Lines<'a> {
41    type Item = Result<Entry<'a>, Error>;
42
43    fn next(&mut self) -> Option<Self::Item> {
44        for line in self.lines.by_ref() {
45            self.line_no += 1;
46            match line.first() {
47                None => continue,
48                Some(b) if *b == b'#' => continue,
49                Some(_) => {}
50            }
51            let line = line.trim();
52            if line.is_empty() {
53                continue;
54            }
55            return parse_line(line.into(), self.line_no).into();
56        }
57        None
58    }
59}
60
61fn parse_line(line: &BStr, line_number: usize) -> Result<Entry<'_>, Error> {
62    let (name1, email1, rest) = parse_name_and_email(line, line_number)?;
63    let (name2, email2, rest) = parse_name_and_email(rest, line_number)?;
64    if !rest.trim().is_empty() {
65        return Err(Error::UnconsumedInput {
66            line_number,
67            line: line.into(),
68        });
69    }
70    Ok(match (name1, email1, name2, email2) {
71        (Some(proper_name), Some(commit_email), None, None) => Entry::change_name_by_email(proper_name, commit_email),
72        (None, Some(proper_email), None, Some(commit_email)) => {
73            Entry::change_email_by_email(proper_email, commit_email)
74        }
75        (Some(proper_name), Some(proper_email), None, Some(commit_email)) => {
76            Entry::change_name_and_email_by_email(proper_name, proper_email, commit_email)
77        }
78        (Some(proper_name), Some(proper_email), Some(commit_name), Some(commit_email)) => {
79            Entry::change_name_and_email_by_name_and_email(proper_name, proper_email, commit_name, commit_email)
80        }
81        _ => {
82            return Err(Error::Malformed {
83                line_number,
84                line: line.into(),
85                message: "Emails without a name or email to map to are invalid".into(),
86            })
87        }
88    })
89}
90
91fn parse_name_and_email(
92    line: &BStr,
93    line_number: usize,
94) -> Result<(Option<&'_ BStr>, Option<&'_ BStr>, &'_ BStr), Error> {
95    match line.find_byte(b'<') {
96        Some(start_bracket) => {
97            let email = &line[start_bracket + 1..];
98            let closing_bracket = email.find_byte(b'>').ok_or_else(|| Error::Malformed {
99                line_number,
100                line: line.into(),
101                message: "Missing closing bracket '>' in email".into(),
102            })?;
103            let email = email[..closing_bracket].trim().as_bstr();
104            if email.is_empty() {
105                return Err(Error::Malformed {
106                    line_number,
107                    line: line.into(),
108                    message: "Email must not be empty".into(),
109                });
110            }
111            let name = line[..start_bracket].trim().as_bstr();
112            let rest = line[start_bracket + closing_bracket + 2..].as_bstr();
113            Ok(((!name.is_empty()).then_some(name), Some(email), rest))
114        }
115        None => Ok((None, None, line)),
116    }
117}