100 lines · 3.6 KB · Rust
RawDownload
/// The error returned by [`parse()`](crate::parse()).pub type Error = gix_error::Exn<gix_error::ValidationError>; use bstr::{BStr, ByteSlice};use gix_error::{ErrorExt, OptionExt, ValidationError}; use crate::Entry; /// An iterator to parse mailmap lines on-demand.pub struct Lines<'a> {    lines: bstr::Lines<'a>,    line_no: usize,} impl<'a> Lines<'a> {    pub(crate) fn new(input: &'a [u8]) -> Self {        Lines {            lines: input.as_bstr().lines(),            line_no: 0,        }    }} impl<'a> Iterator for Lines<'a> {    type Item = Result<Entry<'a>, Error>;     fn next(&mut self) -> Option<Self::Item> {        for line in self.lines.by_ref() {            self.line_no += 1;            match line.first() {                None => continue,                Some(b) if *b == b'#' => continue,                Some(_) => {}            }            let line = line.trim();            if line.is_empty() {                continue;            }            return parse_line(line.into(), self.line_no).into();        }        None    }} fn parse_line(line: &BStr, line_number: usize) -> Result<Entry<'_>, Error> {    let (name1, email1, rest) = parse_name_and_email(line, line_number, false)?;    let (name2, email2, _rest) = parse_name_and_email(rest, line_number, true).unwrap_or((None, None, rest));    if email1.is_none() {        return Err(            ValidationError::new_with_input(format!("Line {line_number} does not contain an email"), line).raise(),        );    }    Ok(match (name1, email1, name2, email2) {        (Some(proper_name), Some(commit_email), None, None) => Entry::change_name_by_email(proper_name, commit_email),        (None, Some(proper_email), None, Some(commit_email)) => {            Entry::change_email_by_email(proper_email, commit_email)        }        (Some(proper_name), Some(proper_email), None, Some(commit_email)) => {            Entry::change_name_and_email_by_email(proper_name, proper_email, commit_email)        }        (Some(proper_name), Some(proper_email), Some(commit_name), Some(commit_email)) => {            Entry::change_name_and_email_by_name_and_email(proper_name, proper_email, commit_name, commit_email)        }        (None, Some(proper_email), Some(commit_name), Some(commit_email)) => {            Entry::change_email_by_name_and_email(proper_email, commit_name, commit_email)        }        _ => {            return Err(ValidationError::new_with_input(                format!("{line_number}: Emails without a name or email to map to are invalid"),                line,            )            .raise());        }    })} fn parse_name_and_email(    line: &BStr,    line_number: usize,    allow_empty_email: bool,) -> Result<(Option<&'_ BStr>, Option<&'_ BStr>, &'_ BStr), Error> {    match line.find_byte(b'<') {        Some(start_bracket) => {            let email = &line[start_bracket + 1..];            let closing_bracket = email.find_byte(b'>').ok_or_raise(|| {                ValidationError::new_with_input(format!("{line_number}: Missing closing bracket '>' in email"), line)            })?;            let email = email[..closing_bracket].trim().as_bstr();            if email.is_empty() && !allow_empty_email {                return Err(                    ValidationError::new_with_input(format!("{line_number}: Email must not be empty"), line).raise(),                );            }            let name = line[..start_bracket].trim().as_bstr();            let rest = line[start_bracket + closing_bracket + 2..].as_bstr();            Ok(((!name.is_empty()).then_some(name), Some(email), rest))        }        None => Ok((None, None, line)),    }}