use serde::de::{Deserialize, Deserializer};
use std::borrow::Cow;
use std::fmt;
use std::fs::File;
use std::io::{BufRead, BufReader, Error as IoError, Lines, Read};
use std::iter::FusedIterator;
use std::path::{Path, PathBuf};
#[derive(Default)]
pub struct StringList {
inner: Vec<StringListEntry>,
}
enum StringListEntry {
Literal(String),
File(PathBuf),
}
impl From<Vec<String>> for StringList {
fn from(input: Vec<String>) -> Self {
let inner = input
.into_iter()
.map(|value| match value.strip_prefix('@') {
Some(value) => StringListEntry::File(value.into()),
None => StringListEntry::Literal(value),
})
.collect();
Self { inner }
}
}
impl<'de> Deserialize<'de> for StringList {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let input: Vec<String> = Deserialize::deserialize(deserializer)?;
Ok(input.into())
}
}
impl StringList {
pub fn iter_values(&self) -> StringListIter {
StringListIter {
list: self,
index: 0,
reader: None,
}
}
}
pub struct StringListIter<'a> {
list: &'a StringList,
index: usize,
reader: Option<StringListReader<'a, File>>,
}
impl<'a> Iterator for StringListIter<'a> {
type Item = (StringListSource<'a>, Result<Cow<'a, str>, IoError>);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(ref mut reader) = self.reader {
if let Some(res) = reader.next() {
return Some((
StringListSource::File {
index: self.index,
path: reader.path,
line: reader.line,
},
match res {
Ok(data) => Ok(data.into()),
Err(err) => Err(err),
},
));
}
self.reader = None;
self.index += 1;
}
match self.list.inner.get(self.index)? {
StringListEntry::Literal(ref data) => {
let source = StringListSource::Literal { index: self.index };
self.index += 1;
return Some((source, Ok(data.into())));
}
StringListEntry::File(ref path) => {
self.reader = match StringListReader::open(path) {
Ok(reader) => Some(reader),
Err(err) => {
return Some((
StringListSource::File {
index: self.index,
path,
line: 0,
},
Err(err),
));
}
};
}
}
}
}
}
impl<'a> FusedIterator for StringListIter<'a> {}
pub enum StringListSource<'a> {
Literal {
index: usize,
},
File {
index: usize,
path: &'a Path,
line: usize,
},
}
impl<'a> fmt::Display for StringListSource<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StringListSource::Literal { index } => write!(f, "#{} (literal)", index + 1),
StringListSource::File { index, path, line } if *line != 0 => {
write!(f, "#{} {:?}:{}", index + 1, path, line)
}
StringListSource::File { index, path, .. } => {
write!(f, "#{} {:?}", index + 1, path)
}
}
}
}
pub struct StringListReader<'a, R> {
inner: Lines<BufReader<R>>,
path: &'a Path,
line: usize,
}
impl<'a> StringListReader<'a, File> {
pub fn open(path: &'a Path) -> Result<Self, IoError> {
Ok(Self::new(File::open(path)?, path))
}
}
impl<'a, R: Read> StringListReader<'a, R> {
pub fn new(inner: R, path: &'a Path) -> Self {
Self {
inner: BufReader::new(inner).lines(),
path,
line: 0,
}
}
}
impl<'a, R: Read> Iterator for StringListReader<'a, R> {
type Item = Result<String, IoError>;
fn next(&mut self) -> Option<Self::Item> {
for data in &mut self.inner {
self.line += 1;
let data = match data {
Err(err) => return Some(Err(err)),
Ok(data) => data,
};
if let Some(data) = data.split('#').next() {
let data = data.trim();
if !data.is_empty() {
return Some(Ok(data.to_owned()));
}
}
}
None
}
}
impl<'a, R: Read> FusedIterator for StringListReader<'a, R> {}
#[cfg(test)]
mod test {
use std::path::PathBuf;
const INPUT: &[u8] = b"\
simple
two words
# comment
additional spacing # ignored
# indented comment
unusual# comment # syntax
";
const EXPECT: &[&str] = &["simple", "two words", "additional spacing", "unusual"];
#[test]
fn test_reader() {
let dummy_path = PathBuf::new();
let mut input_reader = super::StringListReader::new(INPUT, &dummy_path);
let mut expect_iter = EXPECT.iter();
loop {
let input_item = input_reader.next().map(|item| item.unwrap());
let expect_item = expect_iter.next().map(|item| item.to_string());
assert_eq!(input_item, expect_item);
if input_item.is_none() {
break;
}
}
}
}