diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2024-03-07 20:16:32 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-07 20:16:32 -0500 |
commit | 2dfc0aca7c6a04d54fe6f9a73be70fc4c591d552 (patch) | |
tree | 58fb01c46364e4888097e7135b2f829f38ce990c /ext/fs/in_memory_fs.rs | |
parent | 2ed984ba3aa638c3f088ac1edc5c779c7d9195d1 (diff) |
fix(publish): make include and exclude work (#22720)
1. Stops `deno publish` using some custom include/exclude behaviour from
other sub commands
2. Takes ancestor directories into account when resolving gitignore
3. Backards compatible change that adds ability to unexclude an exclude
by using a negated glob at a more specific level for all sub commands
(see https://github.com/denoland/deno_config/pull/44).
Diffstat (limited to 'ext/fs/in_memory_fs.rs')
-rw-r--r-- | ext/fs/in_memory_fs.rs | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/ext/fs/in_memory_fs.rs b/ext/fs/in_memory_fs.rs new file mode 100644 index 000000000..fdd0ad7e7 --- /dev/null +++ b/ext/fs/in_memory_fs.rs @@ -0,0 +1,425 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +// Allow using Arc for this module. +#![allow(clippy::disallowed_types)] + +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::io::Error; +use std::io::ErrorKind; +use std::path::Path; +use std::path::PathBuf; +use std::rc::Rc; +use std::sync::Arc; + +use deno_core::normalize_path; +use deno_core::parking_lot::Mutex; +use deno_io::fs::File; +use deno_io::fs::FsError; +use deno_io::fs::FsResult; +use deno_io::fs::FsStat; + +use crate::interface::FsDirEntry; +use crate::interface::FsFileType; +use crate::FileSystem; +use crate::OpenOptions; + +#[derive(Debug)] +enum PathEntry { + Dir, + File(Vec<u8>), +} + +/// A very basic in-memory file system useful for swapping out in +/// the place of a RealFs for testing purposes. +/// +/// Please develop this out as you need functionality. +#[derive(Debug, Default)] +pub struct InMemoryFs { + entries: Mutex<HashMap<PathBuf, Arc<PathEntry>>>, +} + +impl InMemoryFs { + pub fn setup_text_files(&self, files: Vec<(String, String)>) { + for (path, text) in files { + let path = PathBuf::from(path); + self.mkdir_sync(path.parent().unwrap(), true, 0).unwrap(); + self + .write_file_sync( + &path, + OpenOptions::write(true, false, false, None), + &text.into_bytes(), + ) + .unwrap(); + } + } + + fn get_entry(&self, path: &Path) -> Option<Arc<PathEntry>> { + let path = normalize_path(path); + self.entries.lock().get(&path).cloned() + } +} + +#[async_trait::async_trait(?Send)] +impl FileSystem for InMemoryFs { + fn cwd(&self) -> FsResult<PathBuf> { + Err(FsError::NotSupported) + } + + fn tmp_dir(&self) -> FsResult<PathBuf> { + Err(FsError::NotSupported) + } + + fn chdir(&self, _path: &Path) -> FsResult<()> { + Err(FsError::NotSupported) + } + + fn umask(&self, _mask: Option<u32>) -> FsResult<u32> { + Err(FsError::NotSupported) + } + + fn open_sync( + &self, + _path: &Path, + _options: OpenOptions, + ) -> FsResult<Rc<dyn File>> { + Err(FsError::NotSupported) + } + async fn open_async( + &self, + path: PathBuf, + options: OpenOptions, + ) -> FsResult<Rc<dyn File>> { + self.open_sync(&path, options) + } + + fn mkdir_sync( + &self, + path: &Path, + recursive: bool, + _mode: u32, + ) -> FsResult<()> { + let path = normalize_path(path); + + if let Some(parent) = path.parent() { + let entry = self.entries.lock().get(parent).cloned(); + match entry { + Some(entry) => match &*entry { + PathEntry::File(_) => { + return Err(FsError::Io(Error::new( + ErrorKind::InvalidInput, + "Parent is a file", + ))) + } + PathEntry::Dir => {} + }, + None => { + if recursive { + self.mkdir_sync(parent, true, 0)?; + } else { + return Err(FsError::Io(Error::new( + ErrorKind::NotFound, + "Not found", + ))); + } + } + } + } + + let entry = self.entries.lock().get(&path).cloned(); + match entry { + Some(entry) => match &*entry { + PathEntry::File(_) => Err(FsError::Io(Error::new( + ErrorKind::InvalidInput, + "Is a file", + ))), + PathEntry::Dir => Ok(()), + }, + None => { + self.entries.lock().insert(path, Arc::new(PathEntry::Dir)); + Ok(()) + } + } + } + async fn mkdir_async( + &self, + path: PathBuf, + recursive: bool, + mode: u32, + ) -> FsResult<()> { + self.mkdir_sync(&path, recursive, mode) + } + + fn chmod_sync(&self, _path: &Path, _mode: u32) -> FsResult<()> { + Err(FsError::NotSupported) + } + async fn chmod_async(&self, path: PathBuf, mode: u32) -> FsResult<()> { + self.chmod_sync(&path, mode) + } + + fn chown_sync( + &self, + _path: &Path, + _uid: Option<u32>, + _gid: Option<u32>, + ) -> FsResult<()> { + Err(FsError::NotSupported) + } + async fn chown_async( + &self, + path: PathBuf, + uid: Option<u32>, + gid: Option<u32>, + ) -> FsResult<()> { + self.chown_sync(&path, uid, gid) + } + + fn remove_sync(&self, _path: &Path, _recursive: bool) -> FsResult<()> { + Err(FsError::NotSupported) + } + async fn remove_async(&self, path: PathBuf, recursive: bool) -> FsResult<()> { + self.remove_sync(&path, recursive) + } + + fn copy_file_sync(&self, _from: &Path, _to: &Path) -> FsResult<()> { + Err(FsError::NotSupported) + } + async fn copy_file_async(&self, from: PathBuf, to: PathBuf) -> FsResult<()> { + self.copy_file_sync(&from, &to) + } + + fn cp_sync(&self, _from: &Path, _to: &Path) -> FsResult<()> { + Err(FsError::NotSupported) + } + async fn cp_async(&self, from: PathBuf, to: PathBuf) -> FsResult<()> { + self.cp_sync(&from, &to) + } + + fn stat_sync(&self, path: &Path) -> FsResult<FsStat> { + let entry = self.get_entry(path); + match entry { + Some(entry) => match &*entry { + PathEntry::Dir => Ok(FsStat { + is_file: false, + is_directory: true, + is_symlink: false, + size: 0, + mtime: None, + atime: None, + birthtime: None, + dev: 0, + ino: 0, + mode: 0, + nlink: 0, + uid: 0, + gid: 0, + rdev: 0, + blksize: 0, + blocks: 0, + is_block_device: false, + is_char_device: false, + is_fifo: false, + is_socket: false, + }), + PathEntry::File(data) => Ok(FsStat { + is_file: true, + is_directory: false, + is_symlink: false, + size: data.len() as u64, + mtime: None, + atime: None, + birthtime: None, + dev: 0, + ino: 0, + mode: 0, + nlink: 0, + uid: 0, + gid: 0, + rdev: 0, + blksize: 0, + blocks: 0, + is_block_device: false, + is_char_device: false, + is_fifo: false, + is_socket: false, + }), + }, + None => Err(FsError::Io(Error::new(ErrorKind::NotFound, "Not found"))), + } + } + async fn stat_async(&self, path: PathBuf) -> FsResult<FsStat> { + self.stat_sync(&path) + } + + fn lstat_sync(&self, _path: &Path) -> FsResult<FsStat> { + Err(FsError::NotSupported) + } + async fn lstat_async(&self, path: PathBuf) -> FsResult<FsStat> { + self.lstat_sync(&path) + } + + fn realpath_sync(&self, _path: &Path) -> FsResult<PathBuf> { + Err(FsError::NotSupported) + } + async fn realpath_async(&self, path: PathBuf) -> FsResult<PathBuf> { + self.realpath_sync(&path) + } + + fn read_dir_sync(&self, _path: &Path) -> FsResult<Vec<FsDirEntry>> { + Err(FsError::NotSupported) + } + async fn read_dir_async(&self, path: PathBuf) -> FsResult<Vec<FsDirEntry>> { + self.read_dir_sync(&path) + } + + fn rename_sync(&self, _oldpath: &Path, _newpath: &Path) -> FsResult<()> { + Err(FsError::NotSupported) + } + async fn rename_async( + &self, + oldpath: PathBuf, + newpath: PathBuf, + ) -> FsResult<()> { + self.rename_sync(&oldpath, &newpath) + } + + fn link_sync(&self, _oldpath: &Path, _newpath: &Path) -> FsResult<()> { + Err(FsError::NotSupported) + } + async fn link_async( + &self, + oldpath: PathBuf, + newpath: PathBuf, + ) -> FsResult<()> { + self.link_sync(&oldpath, &newpath) + } + + fn symlink_sync( + &self, + _oldpath: &Path, + _newpath: &Path, + _file_type: Option<FsFileType>, + ) -> FsResult<()> { + Err(FsError::NotSupported) + } + async fn symlink_async( + &self, + oldpath: PathBuf, + newpath: PathBuf, + file_type: Option<FsFileType>, + ) -> FsResult<()> { + self.symlink_sync(&oldpath, &newpath, file_type) + } + + fn read_link_sync(&self, _path: &Path) -> FsResult<PathBuf> { + Err(FsError::NotSupported) + } + async fn read_link_async(&self, path: PathBuf) -> FsResult<PathBuf> { + self.read_link_sync(&path) + } + + fn truncate_sync(&self, _path: &Path, _len: u64) -> FsResult<()> { + Err(FsError::NotSupported) + } + async fn truncate_async(&self, path: PathBuf, len: u64) -> FsResult<()> { + self.truncate_sync(&path, len) + } + + fn utime_sync( + &self, + _path: &Path, + _atime_secs: i64, + _atime_nanos: u32, + _mtime_secs: i64, + _mtime_nanos: u32, + ) -> FsResult<()> { + Err(FsError::NotSupported) + } + async fn utime_async( + &self, + path: PathBuf, + atime_secs: i64, + atime_nanos: u32, + mtime_secs: i64, + mtime_nanos: u32, + ) -> FsResult<()> { + self.utime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos) + } + + fn write_file_sync( + &self, + path: &Path, + options: OpenOptions, + data: &[u8], + ) -> FsResult<()> { + let path = normalize_path(path); + let has_parent_dir = path + .parent() + .and_then(|parent| self.get_entry(parent)) + .map(|e| matches!(*e, PathEntry::Dir)) + .unwrap_or(false); + if !has_parent_dir { + return Err(FsError::Io(Error::new( + ErrorKind::NotFound, + "Parent directory does not exist", + ))); + } + let mut entries = self.entries.lock(); + let entry = entries.entry(path.clone()); + match entry { + Entry::Occupied(mut entry) => { + if let PathEntry::File(existing_data) = &**entry.get() { + if options.create_new { + return Err(FsError::Io(Error::new( + ErrorKind::AlreadyExists, + "File already exists", + ))); + } + if options.append { + let mut new_data = existing_data.clone(); + new_data.extend_from_slice(data); + entry.insert(Arc::new(PathEntry::File(new_data))); + } else { + entry.insert(Arc::new(PathEntry::File(data.to_vec()))); + } + Ok(()) + } else { + Err(FsError::Io(Error::new( + ErrorKind::InvalidInput, + "Not a file", + ))) + } + } + Entry::Vacant(entry) => { + entry.insert(Arc::new(PathEntry::File(data.to_vec()))); + Ok(()) + } + } + } + + async fn write_file_async( + &self, + path: PathBuf, + options: OpenOptions, + data: Vec<u8>, + ) -> FsResult<()> { + self.write_file_sync(&path, options, &data) + } + + fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> { + let entry = self.get_entry(path); + match entry { + Some(entry) => match &*entry { + PathEntry::File(data) => Ok(data.clone()), + PathEntry::Dir => Err(FsError::Io(Error::new( + ErrorKind::InvalidInput, + "Is a directory", + ))), + }, + None => Err(FsError::Io(Error::new(ErrorKind::NotFound, "Not found"))), + } + } + async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> { + self.read_file_sync(&path) + } +} |