use {
crate::{
document::{ConfigOption, Document, SectionKey, SectionRecord},
syntax::Directive,
},
alloc::{borrow::Cow, collections::BTreeMap, string::String, vec::Vec},
hashbrown::HashMap,
};
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct DocumentExport<'a> {
doc: &'a Document,
sections: <&'a BTreeMap<SectionKey, SectionRecord> as IntoIterator>::IntoIter,
options: Option<<&'a HashMap<String, ConfigOption> as IntoIterator>::IntoIter>,
list: Option<ListExport<'a>>,
}
impl<'a> DocumentExport<'a> {
pub(super) fn from_document(file: &'a Document) -> Self {
Self {
sections: file.sections.iter(),
options: None,
list: None,
doc: file,
}
}
fn section(&mut self) -> Option<Directive<'a>> {
let (key, value) = self.sections.next()?;
let name = value.name.as_deref().map(Cow::Borrowed);
let Some(type_name) = self.doc.type_name(key.type_key) else {
unreachable!("state consistency violation: no type name of such key found in interner");
};
self.options = Some(value.section.options.iter());
Some(Directive::Section {
type_: Cow::Borrowed(type_name),
name,
})
}
fn option(&mut self) -> Option<Directive<'a>> {
let (key, option) = self.options.as_mut()?.next()?;
match option {
ConfigOption::Scalar(value) => Some(Directive::Option {
key: Cow::Borrowed(key),
value: Some(Cow::Borrowed(value)),
}),
ConfigOption::List(items) => {
self.list = Some(ListExport {
key,
iter: items.iter(),
});
self.list()
}
}
}
fn list(&mut self) -> Option<Directive<'a>> {
self.list.as_mut()?.next()
}
}
impl<'a> Iterator for DocumentExport<'a> {
type Item = Directive<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.list()
.or_else(|| self.option())
.or_else(|| self.section())
}
}
struct ListExport<'a> {
key: &'a str,
iter: <&'a Vec<String> as IntoIterator>::IntoIter,
}
impl<'a> Iterator for ListExport<'a> {
type Item = Directive<'a>;
fn next(&mut self) -> Option<Self::Item> {
let value = self.iter.next()?;
Some(Directive::List {
key: Cow::Borrowed(self.key),
value: Cow::Borrowed(value),
})
}
}
#[cfg(test)]
mod tests {
use {super::*, crate::document::Section, alloc::borrow::ToOwned};
#[test]
fn document_export() {
let mut doc = Document::default();
doc.merge_or_insert("cat", Some("emily"), {
let mut section = Section::default();
section.insert("foo", ConfigOption::Scalar("bar".to_owned()));
section
})
.unwrap();
let mut export = doc.export();
assert_eq!(
export.next(),
Some(Directive::Section {
type_: "cat".into(),
name: Some("emily".into())
})
);
assert_eq!(
export.next(),
Some(Directive::Option {
key: "foo".into(),
value: Some("bar".into())
})
);
assert!(export.next().is_none());
}
}