pub fn lines_with_offset(s: &str) -> impl Iterator<Item = (&str, usize, usize)> {
let mut offset = 0;
std::iter::from_fn(move || {
if offset >= s.len() {
return None;
}
let start = offset;
loop {
match s[offset..].find(|c| ['\r', '\n'].contains(&c)) {
Some(i) => {
let end = offset + i;
offset = end + 1;
if s.as_bytes().get(end) == Some(&b'\r') {
if s.as_bytes().get(end + 1) != Some(&b'\n') {
continue;
}
offset += 1;
}
return Some((&s[start..end], start, offset));
}
None => {
offset = s.len();
return Some((&s[start..], start, offset));
}
}
}
})
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_lines_with_offset() {
let s = "This string\nhas many\n\nnewlines, including Windows\r\n\r\nand even a \r that \
should not be a newline\n";
let lines = lines_with_offset(s).collect::<Vec<_>>();
assert_eq!(
lines,
&[
("This string", 0, 12),
("has many", 12, 21),
("", 21, 22),
("newlines, including Windows", 22, 51),
("", 51, 53),
("and even a \r that should not be a newline", 53, 95),
]
);
}
}