use std::io::{BufRead, BufReader, Read};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum YamlSplitError {
#[error(transparent)]
IOError(#[from] std::io::Error),
}
pub struct DocumentIterator<R>
where
R: Read,
{
reader: BufReader<R>,
disambiguated: bool,
in_header: bool,
prepend_next: Option<String>,
}
impl<'a, R: Read + 'a> DocumentIterator<R> {
pub fn new(reader: R) -> DocumentIterator<R> {
let br = BufReader::new(reader);
DocumentIterator {
reader: br,
disambiguated: false,
in_header: false,
prepend_next: None,
}
}
}
impl<R: Read> Iterator for DocumentIterator<R> {
type Item = Result<String, YamlSplitError>;
fn next(&mut self) -> Option<Self::Item> {
let mut buf = String::new();
let mut current_file = match &self.prepend_next {
Some(next) => next.clone(),
None => String::new(),
};
self.prepend_next = None;
loop {
if self.disambiguated {
break;
}
buf.clear();
match self.reader.read_line(&mut buf) {
Ok(l) => {
if l == 0 {
return None;
}
for c in buf.chars() {
match c {
' ' | '\t' | '\r' => continue,
'#' | '\n' => break,
'%' => {
self.disambiguated = true;
self.in_header = true;
break;
}
_ => {
self.disambiguated = true;
self.in_header = false;
break;
}
};
}
current_file = current_file + &buf;
}
Err(e) => {
return Some(Err(e.into()));
}
}
}
loop {
buf.clear();
match self.reader.read_line(&mut buf) {
Ok(l) => {
let hit_eof = l == 0;
let cf_len = current_file.len();
if hit_eof && cf_len == 0 {
return None;
}
let end_of_doc = buf.starts_with("...");
let directives_end = buf.starts_with("---");
if !self.in_header && directives_end {
self.in_header = false;
self.prepend_next = Some(buf);
return Some(Ok(current_file));
} else if end_of_doc {
self.in_header = true;
return Some(Ok(current_file));
} else if hit_eof {
return Some(Ok(current_file));
} else if self.in_header && directives_end {
self.in_header = false;
}
current_file = current_file + &buf;
}
Err(e) => {
return Some(Err(e.into()));
}
};
}
}
}
#[cfg(test)]
mod tests {
use crate::DocumentIterator;
use std::io::BufReader;
fn str_reader(s: &[u8]) -> BufReader<&[u8]> {
BufReader::new(s)
}
#[test]
fn bare_document() {
let input = "abc: def";
let reader = str_reader(input.as_bytes());
let mut doc_iter = DocumentIterator::new(reader);
let next = doc_iter.next().unwrap().unwrap();
assert_eq!(&next, "abc: def");
let fin = doc_iter.next().is_none();
assert_eq!(true, fin);
}
#[test]
fn document_with_header() {
let input = r#"
---
abc: def
"#;
let reader = str_reader(input.as_bytes());
let mut doc_iter = DocumentIterator::new(reader);
let next = doc_iter.next().unwrap().unwrap();
assert_eq!(
&next,
r#"
---
abc: def
"#
);
let fin = doc_iter.next().is_none();
assert_eq!(true, fin);
}
#[test]
fn document_with_header_and_directive() {
let input = r#"
%YAML 1.2
---
abc: def
"#;
let reader = str_reader(input.as_bytes());
let mut doc_iter = DocumentIterator::new(reader);
let next = doc_iter.next().unwrap().unwrap();
assert_eq!(
&next,
r#"
%YAML 1.2
---
abc: def
"#
);
let fin = doc_iter.next().is_none();
assert_eq!(true, fin);
}
#[test]
fn two_documents() {
let input = r#"abc: def
---
aaa: bbb
"#;
let reader = str_reader(input.as_bytes());
let mut doc_iter = DocumentIterator::new(reader);
let mut next = doc_iter.next().unwrap().unwrap();
assert_eq!(&next, "abc: def\n");
next = doc_iter.next().unwrap().unwrap();
assert_eq!(
&next,
r#"---
aaa: bbb
"#
);
let fin = doc_iter.next().is_none();
assert_eq!(true, fin);
}
#[test]
fn two_documents_with_headers() {
let input = r#"%YAML 1.2
---
abc: def
...
%YAML 1.2
---
aaa: bbb
"#;
let reader = str_reader(input.as_bytes());
let mut doc_iter = DocumentIterator::new(reader);
let mut next = doc_iter.next().unwrap().unwrap();
assert_eq!(
&next,
r#"%YAML 1.2
---
abc: def
"#
);
next = doc_iter.next().unwrap().unwrap();
assert_eq!(
&next,
r#"
%YAML 1.2
---
aaa: bbb
"#
);
let fin = doc_iter.next().is_none();
assert_eq!(true, fin);
}
#[test]
fn document_medley() {
let input = r#"%YAML 1.2
---
abc: def
---
%YAML: "not a real directive"
---
aaa: bbb
...
---
...
---
final: "document"
"#;
let reader = str_reader(input.as_bytes());
let mut doc_iter = DocumentIterator::new(reader);
let mut next = doc_iter.next().unwrap().unwrap();
assert_eq!(
&next,
r#"%YAML 1.2
---
abc: def
"#
);
next = doc_iter.next().unwrap().unwrap();
assert_eq!(
&next,
r#"---
%YAML: "not a real directive"
"#
);
next = doc_iter.next().unwrap().unwrap();
assert_eq!(
&next,
r#"---
aaa: bbb
"#
);
next = doc_iter.next().unwrap().unwrap();
assert_eq!(
&next,
r#"---
"#
);
next = doc_iter.next().unwrap().unwrap();
assert_eq!(
&next,
r#"---
final: "document"
"#
);
let fin = doc_iter.next().is_none();
assert_eq!(true, fin);
}
}