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