1use core::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 = core::mem::take(&mut self.0);
57 Some((line, None))
58 }
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use alloc::vec;
66 use alloc::vec::Vec;
67
68 #[test]
69 fn non_empty_lines_full_case() {
70 assert_eq!(
71 NonEmptyLines("LF\nCRLF\r\n\r\n\nunterminated")
72 .collect::<Vec<(&str, Option<LineEnding>)>>(),
73 vec![
74 ("LF", Some(LineEnding::LF)),
75 ("CRLF", Some(LineEnding::CRLF)),
76 ("unterminated", None),
77 ]
78 );
79 }
80
81 #[test]
82 fn non_empty_lines_new_lines_only() {
83 assert_eq!(NonEmptyLines("\r\n\n\n\r\n").next(), None);
84 }
85
86 #[test]
87 fn non_empty_lines_no_input() {
88 assert_eq!(NonEmptyLines("").next(), None);
89 }
90}