use std::{
cell::RefCell,
collections::HashMap,
fmt::Display,
fs,
io::{self},
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
};
pub trait FileSystem: Clone {
type FSError: std::fmt::Debug;
fn exists<P: AsRef<Path>>(&self, path: P) -> Result<bool, Self::FSError>;
fn read<P: AsRef<Path>>(&self, filename: P) -> Result<String, Self::FSError>;
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(
&self,
filename: P,
contents: C,
) -> Result<(), Self::FSError>;
}
#[derive(Copy, Clone)]
pub struct RealFileSystem;
impl FileSystem for RealFileSystem {
type FSError = io::Error;
fn exists<P: AsRef<Path>>(&self, path: P) -> Result<bool, Self::FSError> {
Ok(path.as_ref().exists())
}
fn read<P: AsRef<Path>>(&self, filename: P) -> Result<String, Self::FSError> {
fs::read_to_string(filename)
}
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(
&self,
filename: P,
contents: C,
) -> Result<(), Self::FSError> {
fs::write(filename, contents)
}
}
#[derive(Clone)]
pub struct SymbolicFileSystem(Rc<RefCell<HashMap<String, String>>>);
impl FileSystem for SymbolicFileSystem {
type FSError = ();
fn exists<P: AsRef<Path>>(&self, path: P) -> Result<bool, Self::FSError> {
let path_str = path.as_ref().to_str().unwrap_or("").to_string();
Ok(self.0.borrow().contains_key(&path_str))
}
fn read<P: AsRef<Path>>(&self, filename: P) -> Result<String, Self::FSError> {
let path_str = filename.as_ref().to_str().unwrap_or("").to_string();
Ok(self.0.borrow().get(&path_str).cloned().unwrap_or_default())
}
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(
&self,
filename: P,
contents: C,
) -> Result<(), Self::FSError> {
let path_str = filename.as_ref().to_str().unwrap_or("").to_string();
let content_str = String::from_utf8_lossy(contents.as_ref()).to_string();
self.0.borrow_mut().insert(path_str, content_str);
Ok(())
}
}
impl Display for SymbolicFileSystem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "SymbolicFileSystem {{")?;
for (key, value) in self.0.borrow().iter() {
writeln!(f, "file \"{}\": {{|", key)?;
writeln!(f, "{}", value)?;
writeln!(f, "|}}")?;
}
writeln!(f, "}}")
}
}
impl SymbolicFileSystem {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
let mut map = HashMap::new();
let mut to_visit = vec![path.as_ref().to_path_buf()];
while let Some(current_path) = to_visit.pop() {
let metadata = fs::metadata(¤t_path)?;
if metadata.is_dir() {
let dir_entries = fs::read_dir(¤t_path)?;
for entry in dir_entries {
let entry = entry?;
to_visit.push(entry.path());
}
} else {
let contents = fs::read_to_string(¤t_path)?;
let canonical_path = fs::canonicalize(¤t_path)?.to_str().unwrap().to_string();
map.insert(canonical_path, contents);
}
}
Ok(SymbolicFileSystem(Rc::new(RefCell::new(map))))
}
pub fn get(&self, path: &str) -> String {
let path_buf = PathBuf::from(path);
let canonical_path = fs::canonicalize(&path_buf)
.ok()
.and_then(|p| p.to_str().map(String::from))
.unwrap_or_else(|| path.to_string());
self.0.borrow().get(&canonical_path).cloned().unwrap_or_default()
}
}
#[derive(Debug, Clone)]
pub struct FileLoader<T: FileSystem>(T);
unsafe impl<T: FileSystem> Send for FileLoader<T> {}
unsafe impl<T: FileSystem> Sync for FileLoader<T> {}
impl<T: FileSystem> FileLoader<T> {
pub fn new(fs: T) -> Self {
FileLoader(fs)
}
pub fn file_exists(&self, path: &Path) -> bool {
self.0.exists(path).unwrap_or(false)
}
pub fn read_file(&self, path: &Path) -> io::Result<String> {
self.0.read(path).map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e)))
}
pub fn read_binary_file(&self, path: &Path) -> io::Result<Arc<[u8]>> {
let content = self.read_file(path)?;
Ok(Arc::from(content.into_bytes().as_slice()))
}
}