use std::mem;
pub use monitor::{
Monitor, MonitorConfig, MonitorDirectories, MonitorEntry, MonitorMessage, NotifyMonitor,
};
use mun_paths::{AbsPath, AbsPathBuf};
use path_interner::PathInterner;
mod monitor;
mod path_interner;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct FileId(pub u32);
#[derive(Default)]
pub struct VirtualFileSystem {
interner: PathInterner,
file_contents: Vec<Option<Vec<u8>>>,
changes: Vec<ChangedFile>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ChangedFile {
pub file_id: FileId,
pub kind: ChangeKind,
}
impl ChangedFile {
pub fn is_created_or_deleted(&self) -> bool {
matches!(self.kind, ChangeKind::Create | ChangeKind::Delete)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ChangeKind {
Create,
Modify,
Delete,
}
impl VirtualFileSystem {
pub fn has_changes(&self) -> bool {
!self.changes.is_empty()
}
pub fn take_changes(&mut self) -> Vec<ChangedFile> {
mem::take(&mut self.changes)
}
pub fn file_id(&self, path: &AbsPath) -> Option<FileId> {
self.interner
.get(path)
.filter(|&file_id| self.get(file_id).is_some())
}
pub fn file_path(&self, file_id: FileId) -> &AbsPath {
self.interner.lookup(file_id)
}
pub fn file_contents(&self, file_id: FileId) -> Option<&[u8]> {
self.get(file_id).as_deref()
}
pub fn iter(&self) -> impl Iterator<Item = (FileId, &AbsPath)> + '_ {
self.file_contents
.iter()
.enumerate()
.filter(|(_, contents)| contents.is_some())
.map(move |(id, _)| {
let file_id = FileId(id as u32);
let path = self.interner.lookup(file_id);
(file_id, path)
})
}
pub fn set_file_contents(&mut self, path: &AbsPath, contents: Option<Vec<u8>>) -> bool {
let file_id = self.alloc_file_id(path);
let kind = match (&self.get(file_id), &contents) {
(None, None) => return false,
(None, Some(_)) => ChangeKind::Create,
(Some(_), None) => ChangeKind::Delete,
(Some(old), Some(new)) if old == new => return false,
(Some(_), Some(_)) => ChangeKind::Modify,
};
*self.get_mut(file_id) = contents;
self.changes.push(ChangedFile { file_id, kind });
true
}
fn alloc_file_id(&mut self, path: &AbsPath) -> FileId {
let file_id = self.interner.intern(path);
let idx = file_id.0 as usize;
let len = self.file_contents.len().max(idx + 1);
self.file_contents.resize(len, None);
file_id
}
fn get(&self, file_id: FileId) -> &Option<Vec<u8>> {
&self.file_contents[file_id.0 as usize]
}
fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> {
&mut self.file_contents[file_id.0 as usize]
}
}
#[cfg(test)]
mod tests {
use std::convert::TryInto;
use std::path::PathBuf;
use crate::{AbsPathBuf, ChangeKind, ChangedFile, VirtualFileSystem};
#[test]
fn vfs() {
let mut vfs = VirtualFileSystem::default();
assert!(!vfs.has_changes());
let abs_manifest_dir: AbsPathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.try_into()
.unwrap();
let test_path = abs_manifest_dir.as_path().join("test");
assert!(vfs.file_id(&test_path).is_none());
assert!(vfs.set_file_contents(&test_path, Some(vec![])));
assert!(vfs.has_changes());
let file_id = vfs
.file_id(&test_path)
.expect("there should be a FileId by now");
assert_eq!(&test_path, vfs.file_path(file_id));
assert!(vfs.file_contents(file_id).is_some());
assert!(!vfs.set_file_contents(&test_path, Some(vec![])));
assert!(vfs.set_file_contents(&test_path, Some(vec![0])));
assert!(vfs.set_file_contents(&test_path, None));
assert_eq!(vfs.file_id(&test_path), None);
assert!(vfs.has_changes());
assert_eq!(
vfs.take_changes(),
vec![
ChangedFile {
file_id,
kind: ChangeKind::Create
},
ChangedFile {
file_id,
kind: ChangeKind::Modify
},
ChangedFile {
file_id,
kind: ChangeKind::Delete
},
]
);
}
#[test]
fn iter() {
let mut vfs = VirtualFileSystem::default();
let abs_manifest_dir: AbsPathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.try_into()
.unwrap();
let test_path2 = abs_manifest_dir.as_path().join("test2");
let test_path = abs_manifest_dir.as_path().join("test");
assert!(vfs.set_file_contents(&test_path, Some(vec![0])));
assert!(vfs.set_file_contents(&test_path2, Some(vec![1])));
let file_id = vfs.file_id(&test_path).unwrap();
let file_id2 = vfs.file_id(&test_path2).unwrap();
assert_ne!(file_id, file_id2);
let mut entries = vfs
.iter()
.map(|(id, entry)| (id, entry.to_path_buf()))
.collect::<Vec<_>>();
let mut expected_entries =
vec![(file_id, test_path.clone()), (file_id2, test_path2.clone())];
entries.sort_by_key(|entry| entry.0);
expected_entries.sort_by_key(|entry| entry.0);
assert_eq!(entries, expected_entries);
}
}