use std::{
error::Error,
fmt,
path::{Path, PathBuf},
};
pub trait FileResolver {
fn push_path(&mut self, path: &Path) -> Result<(), ResolverError>;
fn pop_path(&mut self);
fn current_path(&self) -> Option<&PathBuf>;
fn read_to_string(&self) -> Result<String, ResolverError>;
}
#[derive(Debug, Clone)]
pub enum ResolverError {
DuplicateInclude(PathBuf),
Io(PathBuf, String),
}
impl fmt::Display for ResolverError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Io(p, e) => write!(f, "i/o failure '{}': {}", p.display(), e),
Self::DuplicateInclude(p) => write!(f, "duplicate include '{}'", p.display()),
}
}
}
impl Error for ResolverError {}
pub struct StandardFileResolver {
files: Vec<PathBuf>,
}
impl StandardFileResolver {
pub fn new() -> Self {
Self { files: Vec::new() }
}
}
impl FileResolver for StandardFileResolver {
fn push_path(&mut self, path: &Path) -> Result<(), ResolverError> {
let mut full_path = if self.files.len() == 0 {
let current_dir = std::env::current_dir()
.map_err(|e| ResolverError::Io(path.to_path_buf(), e.to_string()))?;
current_dir.join(path)
} else {
self.current_path().unwrap().parent().unwrap().join(path)
};
full_path = path_clean::clean(full_path);
if self.files.iter().find(|path| *path == &full_path).is_none() {
self.files.push(full_path);
Ok(())
} else {
Err(ResolverError::DuplicateInclude(full_path))
}
}
fn pop_path(&mut self) {
self.files.pop();
}
fn current_path(&self) -> Option<&PathBuf> {
self.files.last()
}
fn read_to_string(&self) -> Result<String, ResolverError> {
let file_path = self.current_path().unwrap();
match std::fs::read_to_string(file_path) {
Ok(contents) => Ok(contents),
Err(e) => Err(ResolverError::Io(file_path.to_path_buf(), e.to_string())),
}
}
}