1use std::fmt::Debug;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum LineEnding {
9 CRLF,
13 LF,
16}
17
18impl LineEnding {
19 #[inline]
21 pub const fn as_str(&self) -> &'static str {
22 match self {
23 Self::CRLF => "\r\n",
24 Self::LF => "\n",
25 }
26 }
27}
28
29#[derive(Debug, Clone, Copy)]
35pub(crate) struct NonEmptyLines<'a>(pub &'a str);
36
37impl<'a> Iterator for NonEmptyLines<'a> {
38 type Item = (&'a str, Option<LineEnding>);
39
40 fn next(&mut self) -> Option<Self::Item> {
41 while let Some(lf) = self.0.find('\n') {
42 if lf == 0 || (lf == 1 && self.0.as_bytes()[lf - 1] == b'\r') {
43 self.0 = &self.0[(lf + 1)..];
44 continue;
45 }
46 let trimmed = match self.0.as_bytes()[lf - 1] {
47 b'\r' => (&self.0[..(lf - 1)], Some(LineEnding::CRLF)),
48 _ => (&self.0[..lf], Some(LineEnding::LF)),
49 };
50 self.0 = &self.0[(lf + 1)..];
51 return Some(trimmed);
52 }
53 if self.0.is_empty() {
54 None
55 } else {
56 let line = std::mem::take(&mut self.0);
57 Some((line, None))
58 }
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn non_empty_lines_full_case() {
68 assert_eq!(
69 NonEmptyLines("LF\nCRLF\r\n\r\n\nunterminated")
70 .collect::<Vec<(&str, Option<LineEnding>)>>(),
71 vec![
72 ("LF", Some(LineEnding::LF)),
73 ("CRLF", Some(LineEnding::CRLF)),
74 ("unterminated", None),
75 ]
76 );
77 }
78
79 #[test]
80 fn non_empty_lines_new_lines_only() {
81 assert_eq!(NonEmptyLines("\r\n\n\n\r\n").next(), None);
82 }
83
84 #[test]
85 fn non_empty_lines_no_input() {
86 assert_eq!(NonEmptyLines("").next(), None);
87 }
88}