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