Skip to main content

indigo_core/
fs.rs

1//! Abstract filesystem interface.
2
3use anyhow::anyhow;
4use camino::{Utf8Path, Utf8PathBuf};
5use indigo_kernel::IndigoBuildHasher;
6use std::{
7    collections::HashMap,
8    fs,
9    sync::{Arc, Mutex},
10};
11
12pub trait Fs {
13    fn read(&self, path: &Utf8Path) -> anyhow::Result<Vec<u8>>;
14
15    fn write(&self, path: &Utf8Path, bytes: &[u8]) -> anyhow::Result<()>;
16
17    fn exists(&self, path: &Utf8Path) -> anyhow::Result<bool>;
18}
19
20const _ASSERT_FS_IS_DYN_COMPATIBLE: Option<&dyn Fs> = None;
21
22impl<T: Fs + ?Sized> Fs for Arc<T> {
23    fn read(&self, path: &Utf8Path) -> anyhow::Result<Vec<u8>> {
24        T::read(self, path)
25    }
26    fn write(&self, path: &Utf8Path, bytes: &[u8]) -> anyhow::Result<()> {
27        T::write(self, path, bytes)
28    }
29    fn exists(&self, path: &Utf8Path) -> anyhow::Result<bool> {
30        T::exists(self, path)
31    }
32}
33
34/// Trivial filesystem implementation that panics if you do anything. This is the default filesystem
35/// implementation for `Editor`.
36pub struct NoFs;
37
38impl Fs for NoFs {
39    fn read(&self, _path: &Utf8Path) -> anyhow::Result<Vec<u8>> {
40        panic!("No filesystem implementation configured");
41    }
42
43    fn write(&self, _path: &Utf8Path, _bytes: &[u8]) -> anyhow::Result<()> {
44        panic!("No filesystem implementation configured");
45    }
46
47    fn exists(&self, _path: &Utf8Path) -> anyhow::Result<bool> {
48        panic!("No filesystem implementation configured");
49    }
50}
51
52/// Real filesystem implementation that uses functions from `std::fs`.
53pub struct RealFs;
54
55impl Fs for RealFs {
56    fn read(&self, path: &Utf8Path) -> anyhow::Result<Vec<u8>> {
57        Ok(fs::read(path)?)
58    }
59
60    fn write(&self, path: &Utf8Path, bytes: &[u8]) -> anyhow::Result<()> {
61        Ok(fs::write(path, bytes)?)
62    }
63
64    fn exists(&self, path: &Utf8Path) -> anyhow::Result<bool> {
65        Ok(fs::exists(path)?)
66    }
67}
68
69/// In-memory filesystem implementation for testing. Currently only supports canonicalized paths
70/// (i.e. absolute, normalized, and no symlinks).
71#[derive(Default)]
72pub struct TestFs {
73    pub files: Arc<Mutex<HashMap<Utf8PathBuf, Vec<u8>, IndigoBuildHasher>>>,
74}
75
76impl Fs for TestFs {
77    fn read(&self, path: &Utf8Path) -> anyhow::Result<Vec<u8>> {
78        let files = self.files.lock().unwrap();
79        if let Some(bytes) = files.get(path) {
80            Ok(bytes.clone())
81        } else {
82            Err(anyhow!("File not found: `{path}`"))
83        }
84    }
85
86    fn write(&self, path: &Utf8Path, bytes: &[u8]) -> anyhow::Result<()> {
87        let mut files = self.files.lock().unwrap();
88        files.insert(path.to_path_buf(), bytes.to_vec());
89        Ok(())
90    }
91
92    fn exists(&self, path: &Utf8Path) -> anyhow::Result<bool> {
93        let files = self.files.lock().unwrap();
94        let exists = files.contains_key(path);
95        Ok(exists)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test() -> anyhow::Result<()> {
105        let fs = TestFs::default();
106        fs.write("foo.rs".into(), b"fn foo() {}")?;
107        fs.write("bar.rs".into(), b"fn bar() {}")?;
108        assert!(fs.exists("foo.rs".into())?);
109        assert_eq!(&fs.read("foo.rs".into())?, b"fn foo() {}");
110        assert_eq!(&fs.read("bar.rs".into())?, b"fn bar() {}");
111        assert!(fs.read("baz.rs".into()).is_err());
112        assert!(!fs.exists("baz.rs".into())?);
113        fs.write("foo.rs".into(), b"fn foo() { panic!() }")?;
114        assert_eq!(&fs.read("foo.rs".into())?, b"fn foo() { panic!() }");
115        Ok(())
116    }
117}