rem_utils/
filesystem.rs

1use std::{
2    cell::RefCell,
3    collections::HashMap,
4    fmt::Display,
5    fs,
6    io::{self},
7    path::{Path, PathBuf},
8    rc::Rc,
9    sync::Arc,
10};
11
12/// Representation of a file system
13pub trait FileSystem: Clone {
14    /// Errors
15    type FSError: std::fmt::Debug;
16
17    /// Tests for the existence of a given file
18    fn exists<P: AsRef<Path>>(&self, path: P) -> Result<bool, Self::FSError>;
19
20    /// Reads a file, specified by a path into a string
21    fn read<P: AsRef<Path>>(&self, filename: P) -> Result<String, Self::FSError>;
22
23    /// Writes into a file specified by a path
24    fn write<P: AsRef<Path>, C: AsRef<[u8]>>(
25        &self,
26        filename: P,
27        contents: C,
28    ) -> Result<(), Self::FSError>;
29}
30
31/// Wrapper over the underlying file system
32#[derive(Copy, Clone)]
33pub struct RealFileSystem;
34
35impl FileSystem for RealFileSystem {
36    type FSError = io::Error;
37
38    fn exists<P: AsRef<Path>>(&self, path: P) -> Result<bool, Self::FSError> {
39        Ok(path.as_ref().exists())
40    }
41
42    fn read<P: AsRef<Path>>(&self, filename: P) -> Result<String, Self::FSError> {
43        fs::read_to_string(filename)
44    }
45
46    fn write<P: AsRef<Path>, C: AsRef<[u8]>>(
47        &self,
48        filename: P,
49        contents: C,
50    ) -> Result<(), Self::FSError> {
51        fs::write(filename, contents)
52    }
53}
54
55#[derive(Clone)]
56pub struct SymbolicFileSystem(Rc<RefCell<HashMap<String, String>>>);
57
58impl FileSystem for SymbolicFileSystem {
59    type FSError = ();
60
61    fn exists<P: AsRef<Path>>(&self, path: P) -> Result<bool, Self::FSError> {
62        let path_str = path.as_ref().to_str().unwrap_or("").to_string();
63        Ok(self.0.borrow().contains_key(&path_str))
64    }
65
66    fn read<P: AsRef<Path>>(&self, filename: P) -> Result<String, Self::FSError> {
67        let path_str = filename.as_ref().to_str().unwrap_or("").to_string();
68        Ok(self.0.borrow().get(&path_str).cloned().unwrap_or_default())
69    }
70
71    fn write<P: AsRef<Path>, C: AsRef<[u8]>>(
72        &self,
73        filename: P,
74        contents: C,
75    ) -> Result<(), Self::FSError> {
76        let path_str = filename.as_ref().to_str().unwrap_or("").to_string();
77        let content_str = String::from_utf8_lossy(contents.as_ref()).to_string();
78        self.0.borrow_mut().insert(path_str, content_str);
79        Ok(())
80    }
81}
82
83impl Display for SymbolicFileSystem {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        writeln!(f, "SymbolicFileSystem {{")?;
86        for (key, value) in self.0.borrow().iter() {
87            writeln!(f, "file \"{}\": {{|", key)?;
88            writeln!(f, "{}", value)?;
89            writeln!(f, "|}}")?;
90        }
91        writeln!(f, "}}")
92    }
93}
94
95impl SymbolicFileSystem {
96    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
97        let mut map = HashMap::new();
98        let mut to_visit = vec![path.as_ref().to_path_buf()];
99
100        while let Some(current_path) = to_visit.pop() {
101            let metadata = fs::metadata(&current_path)?;
102            if metadata.is_dir() {
103                let dir_entries = fs::read_dir(&current_path)?;
104                for entry in dir_entries {
105                    let entry = entry?;
106                    to_visit.push(entry.path());
107                }
108            } else {
109                let contents = fs::read_to_string(&current_path)?;
110                let canonical_path = fs::canonicalize(&current_path)?.to_str().unwrap().to_string();
111                map.insert(canonical_path, contents);
112            }
113        }
114
115        Ok(SymbolicFileSystem(Rc::new(RefCell::new(map))))
116    }
117
118    pub fn get(&self, path: &str) -> String {
119        let path_buf = PathBuf::from(path);
120        let canonical_path = fs::canonicalize(&path_buf)
121            .ok()
122            .and_then(|p| p.to_str().map(String::from))
123            .unwrap_or_else(|| path.to_string());
124        self.0.borrow().get(&canonical_path).cloned().unwrap_or_default()
125    }
126}
127
128#[derive(Debug, Clone)]
129pub struct FileLoader<T: FileSystem>(T);
130
131unsafe impl<T: FileSystem> Send for FileLoader<T> {}
132unsafe impl<T: FileSystem> Sync for FileLoader<T> {}
133
134impl<T: FileSystem> FileLoader<T> {
135    pub fn new(fs: T) -> Self {
136        FileLoader(fs)
137    }
138
139    pub fn file_exists(&self, path: &Path) -> bool {
140        self.0.exists(path).unwrap_or(false)
141    }
142
143    pub fn read_file(&self, path: &Path) -> io::Result<String> {
144        self.0.read(path).map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e)))
145    }
146
147    pub fn read_binary_file(&self, path: &Path) -> io::Result<Arc<[u8]>> {
148        let content = self.read_file(path)?;
149        Ok(Arc::from(content.into_bytes().as_slice()))
150    }
151}