diff options
Diffstat (limited to 'cli/util')
-rw-r--r-- | cli/util/fs.rs | 112 |
1 files changed, 95 insertions, 17 deletions
diff --git a/cli/util/fs.rs b/cli/util/fs.rs index b17e2d2d9..6c1f304d6 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -26,19 +26,79 @@ use crate::util::progress_bar::ProgressMessagePrompt; use super::path::specifier_to_file_path; +/// Writes the file to the file system at a temporary path, then +/// renames it to the destination in a single sys call in order +/// to never leave the file system in a corrupted state. +/// +/// This also handles creating the directory if a NotFound error +/// occurs. pub fn atomic_write_file<T: AsRef<[u8]>>( - filename: &Path, + file_path: &Path, data: T, mode: u32, ) -> std::io::Result<()> { - let rand: String = (0..4) - .map(|_| format!("{:02x}", rand::random::<u8>())) - .collect(); - let extension = format!("{rand}.tmp"); - let tmp_file = filename.with_extension(extension); - write_file(&tmp_file, data, mode)?; - std::fs::rename(tmp_file, filename)?; - Ok(()) + fn atomic_write_file_raw( + temp_file_path: &Path, + file_path: &Path, + data: &[u8], + mode: u32, + ) -> std::io::Result<()> { + write_file(temp_file_path, data, mode)?; + std::fs::rename(temp_file_path, file_path)?; + Ok(()) + } + + fn add_file_context(file_path: &Path, err: Error) -> Error { + Error::new( + err.kind(), + format!("{:#} (for '{}')", err, file_path.display()), + ) + } + + fn inner(file_path: &Path, data: &[u8], mode: u32) -> std::io::Result<()> { + let temp_file_path = { + let rand: String = (0..4) + .map(|_| format!("{:02x}", rand::random::<u8>())) + .collect(); + let extension = format!("{rand}.tmp"); + file_path.with_extension(extension) + }; + + if let Err(write_err) = + atomic_write_file_raw(&temp_file_path, file_path, data, mode) + { + if write_err.kind() == ErrorKind::NotFound { + let parent_dir_path = file_path.parent().unwrap(); + match std::fs::create_dir_all(parent_dir_path) { + Ok(()) => { + return atomic_write_file_raw( + &temp_file_path, + file_path, + data, + mode, + ) + .map_err(|err| add_file_context(file_path, err)); + } + Err(create_err) => { + if !parent_dir_path.exists() { + return Err(Error::new( + create_err.kind(), + format!( + "{:#} (for '{}')\nCheck the permission of the directory.", + create_err, + parent_dir_path.display() + ), + )); + } + } + } + } + return Err(add_file_context(file_path, write_err)); + } + Ok(()) + } + + inner(file_path, data.as_ref(), mode) } pub fn write_file<T: AsRef<[u8]>>( @@ -140,6 +200,7 @@ pub struct FileCollector<TFilter: Fn(&Path) -> bool> { file_filter: TFilter, ignore_git_folder: bool, ignore_node_modules: bool, + ignore_deno_modules: bool, } impl<TFilter: Fn(&Path) -> bool> FileCollector<TFilter> { @@ -149,6 +210,7 @@ impl<TFilter: Fn(&Path) -> bool> FileCollector<TFilter> { file_filter, ignore_git_folder: false, ignore_node_modules: false, + ignore_deno_modules: false, } } @@ -165,6 +227,11 @@ impl<TFilter: Fn(&Path) -> bool> FileCollector<TFilter> { self } + pub fn ignore_deno_modules(mut self) -> Self { + self.ignore_deno_modules = true; + self + } + pub fn ignore_git_folder(mut self) -> Self { self.ignore_git_folder = true; self @@ -203,9 +270,12 @@ impl<TFilter: Fn(&Path) -> bool> FileCollector<TFilter> { .file_name() .map(|dir_name| { let dir_name = dir_name.to_string_lossy().to_lowercase(); - let is_ignored_file = self.ignore_node_modules - && dir_name == "node_modules" - || self.ignore_git_folder && dir_name == ".git"; + let is_ignored_file = match dir_name.as_str() { + "node_modules" => self.ignore_node_modules, + "deno_modules" => self.ignore_deno_modules, + ".git" => self.ignore_git_folder, + _ => false, + }; // allow the user to opt out of ignoring by explicitly specifying the dir file != c && is_ignored_file }) @@ -238,7 +308,8 @@ pub fn collect_specifiers( let file_collector = FileCollector::new(predicate) .add_ignore_paths(&files.exclude) .ignore_git_folder() - .ignore_node_modules(); + .ignore_node_modules() + .ignore_deno_modules(); let root_path = current_dir()?; let include_files = if files.include.is_empty() { @@ -657,10 +728,12 @@ mod tests { // ├── a.ts // ├── b.js // ├── child - // | ├── node_modules - // | | └── node_modules.js + // | ├── deno_modules + // | | └── deno_modules.js // | ├── git // | | └── git.js + // | ├── node_modules + // | | └── node_modules.js // │ ├── e.mjs // │ ├── f.mjsx // │ ├── .foo.TS @@ -685,6 +758,8 @@ mod tests { t.write("dir.ts/child/node_modules/node_modules.js", ""); t.create_dir_all("dir.ts/child/.git"); t.write("dir.ts/child/.git/git.js", ""); + t.create_dir_all("dir.ts/child/deno_modules"); + t.write("dir.ts/child/deno_modules/deno_modules.js", ""); let ignore_dir_path = root_dir_path.join("ignore"); let ignore_dir_files = ["g.d.ts", ".gitignore"]; @@ -709,6 +784,7 @@ mod tests { "b.js", "c.tsx", "d.jsx", + "deno_modules.js", "e.mjs", "f.mjsx", "git.js", @@ -722,8 +798,10 @@ mod tests { assert_eq!(file_names, expected); // test ignoring the .git and node_modules folder - let file_collector = - file_collector.ignore_git_folder().ignore_node_modules(); + let file_collector = file_collector + .ignore_git_folder() + .ignore_node_modules() + .ignore_deno_modules(); let result = file_collector .collect_files(&[root_dir_path.to_path_buf()]) .unwrap(); |