use std::ops::Add;
pub(crate) struct WholeLinesIter<'a, I: Iterator<Item = &'a str>> {
lines: I,
current: Option<&'a str>,
}
impl<'a, I: Iterator<Item = &'a str>> WholeLinesIter<'a, I> {
pub(crate) fn new(mut lines: I) -> Self {
Self {
current: lines.next(),
lines,
}
}
fn consume_complete_value(&mut self, first_line: &'a str) -> String {
let mut value = vec![first_line];
loop {
if let Some(line) = self.lines.next() {
if line.starts_with(' ') {
value.push(line.trim_start());
} else {
self.current = Some(line);
break;
}
} else {
self.current = None;
break;
}
}
join_lines(value)
}
}
fn join_lines(v: Vec<&str>) -> String {
v.into_iter().fold(String::new(), |acc, e| {
if acc.ends_with('-') || acc.ends_with(' ') || acc.is_empty() {
acc
} else {
acc.add(" ")
}
.add(e)
})
}
impl<'a, I: Iterator<Item = &'a str>> Iterator for WholeLinesIter<'a, I> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
self.current.map(|x| self.consume_complete_value(x))
}
}
#[cfg(test)]
mod tests {
use super::*;
use itertools::Itertools;
use rstest::*;
#[rstest]
#[case("", &[""])]
#[case(r#"PMID- 123456
FOO - bar
BOB - Alice"#, &["PMID- 123456", "FOO - bar", "BOB - Alice"])]
#[
case(r#"PMID- 123456
FOO - bar
LONG- I am a very long line containing so
much text that there is a line break"#,
&["PMID- 123456", "FOO - bar", "LONG- I am a very long line containing so much text that there is a line break"])]
#[
case(r#"PMID- 123456
LONG- Self-
assembled structures are important"#,
&["PMID- 123456", "LONG- Self-assembled structures are important"])]
#[
case(r#"PMID- 123456
FOO - bar
LONG- I am a very long line containing so
much text that there is a line break
LAST- line
"#,
&["PMID- 123456", "FOO - bar", "LONG- I am a very long line containing so much text that there is a line break", "LAST- line", ""])]
fn test_continued_lines_iterator(#[case] text: &str, #[case] expected: &[&str]) {
let actual: Vec<_> = WholeLinesIter::new(text.split('\n')).collect();
assert_eq!(&actual.iter().map(|s| s.as_str()).collect_vec(), expected)
}
}