//! Filesystem utilities //! //! These are will be parallel if the `parallel` feature is enabled, at the expense of compiling additional dependencies //! along with runtime costs for maintaining a global [`rayon`](https://docs.rs/rayon) thread pool. //! //! For information on how to use the [`WalkDir`] type, have a look at // TODO: Move all this to `gix-fs` in a breaking change. #[cfg(feature = "walkdir")] mod shared { /// The desired level of parallelism. pub enum Parallelism { /// Do not parallelize at all by making a serial traversal on the current thread. Serial, /// Create a new thread pool for each traversal with up to 16 threads or the amount of logical cores of the machine. ThreadPoolPerTraversal { /// The base name of the threads we create as part of the thread-pool. thread_name: &'static str, }, } } #[cfg(any(feature = "walkdir", feature = "fs-read-dir"))] mod walkdir_precompose { use std::{borrow::Cow, ffi::OsStr, path::Path}; #[derive(Debug)] pub struct DirEntry { inner: T, precompose_unicode: bool, } impl DirEntry { /// Create a new instance. pub fn new(inner: T, precompose_unicode: bool) -> Self { Self { inner, precompose_unicode, } } } pub trait DirEntryApi { fn path(&self) -> Cow<'_, Path>; fn file_name(&self) -> Cow<'_, OsStr>; fn file_type(&self) -> std::io::Result; } impl DirEntry { /// Obtain the full path of this entry, possibly with precomposed unicode if enabled. /// /// Note that decomposing filesystem like those made by Apple accept both precomposed and /// decomposed names, and consider them equal. pub fn path(&self) -> Cow<'_, Path> { let path = self.inner.path(); if self.precompose_unicode { gix_utils::str::precompose_path(path) } else { path } } /// Obtain filen name of this entry, possibly with precomposed unicode if enabled. pub fn file_name(&self) -> Cow<'_, OsStr> { let name = self.inner.file_name(); if self.precompose_unicode { gix_utils::str::precompose_os_string(name) } else { name } } /// Return the file type for the file that this entry points to. /// /// If `follow_links` was `true`, this is the file type of the item the link points to. pub fn file_type(&self) -> std::io::Result { self.inner.file_type() } } /// A platform over entries in a directory, which may or may not precompose unicode after retrieving /// paths from the file system. #[cfg(feature = "walkdir")] pub struct WalkDir { pub(crate) inner: Option, pub(crate) precompose_unicode: bool, } #[cfg(feature = "walkdir")] pub struct WalkDirIter where T: Iterator>, I: DirEntryApi, { pub(crate) inner: T, pub(crate) precompose_unicode: bool, } #[cfg(feature = "walkdir")] impl Iterator for WalkDirIter where T: Iterator>, I: DirEntryApi + std::fmt::Debug, { type Item = Result, E>; fn next(&mut self) -> Option { self.inner .next() .map(|res| res.map(|entry| DirEntry::new(entry, self.precompose_unicode))) } } } /// #[cfg(feature = "fs-read-dir")] pub mod read_dir { use std::{borrow::Cow, ffi::OsStr, fs::FileType, path::Path}; /// A directory entry adding precompose-unicode support to [`std::fs::DirEntry`]. pub type DirEntry = super::walkdir_precompose::DirEntry; impl super::walkdir_precompose::DirEntryApi for std::fs::DirEntry { fn path(&self) -> Cow<'_, Path> { self.path().into() } fn file_name(&self) -> Cow<'_, OsStr> { self.file_name().into() } fn file_type(&self) -> std::io::Result { self.file_type() } } } /// #[cfg(feature = "walkdir")] pub mod walkdir { use std::{borrow::Cow, ffi::OsStr, fs::FileType, path::Path}; pub use walkdir::Error; use walkdir::{DirEntry as DirEntryImpl, WalkDir as WalkDirImpl}; /// A directory entry returned by [DirEntryIter]. pub type DirEntry = super::walkdir_precompose::DirEntry; /// A platform to create a [DirEntryIter] from. pub type WalkDir = super::walkdir_precompose::WalkDir; pub use super::shared::Parallelism; impl super::walkdir_precompose::DirEntryApi for DirEntryImpl { fn path(&self) -> Cow<'_, Path> { self.path().into() } fn file_name(&self) -> Cow<'_, OsStr> { self.file_name().into() } fn file_type(&self) -> std::io::Result { Ok(self.file_type()) } } impl IntoIterator for WalkDir { type Item = Result; type IntoIter = DirEntryIter; fn into_iter(self) -> Self::IntoIter { DirEntryIter { inner: self.inner.expect("always set (builder fix)").into_iter(), precompose_unicode: self.precompose_unicode, } } } impl WalkDir { /// Set the minimum component depth of paths of entries. pub fn min_depth(mut self, min: usize) -> Self { self.inner = Some(self.inner.take().expect("always set").min_depth(min)); self } /// Set the maximum component depth of paths of entries. pub fn max_depth(mut self, max: usize) -> Self { self.inner = Some(self.inner.take().expect("always set").max_depth(max)); self } /// Follow symbolic links. pub fn follow_links(mut self, toggle: bool) -> Self { self.inner = Some(self.inner.take().expect("always set").follow_links(toggle)); self } } /// Instantiate a new directory iterator which will not skip hidden files, with the given level of `parallelism`. /// /// Use `precompose_unicode` to represent the `core.precomposeUnicode` configuration option. pub fn walkdir_new(root: &Path, _: Parallelism, precompose_unicode: bool) -> WalkDir { WalkDir { inner: WalkDirImpl::new(root).into(), precompose_unicode, } } /// Instantiate a new directory iterator which will not skip hidden files and is sorted, with the given level of `parallelism`. /// /// Use `precompose_unicode` to represent the `core.precomposeUnicode` configuration option. /// Use `max_depth` to limit the depth of the recursive walk. /// * `0` /// - Returns only the root path with no children /// * `1` /// - Root directory and children. /// * `1..n` /// - Root directory, children and {n}-grandchildren pub fn walkdir_sorted_new(root: &Path, _: Parallelism, max_depth: usize, precompose_unicode: bool) -> WalkDir { WalkDir { inner: WalkDirImpl::new(root) .max_depth(max_depth) .sort_by(|a, b| { let storage_a; let storage_b; let a_name = match gix_path::os_str_into_bstr(a.file_name()) { Ok(f) => f, Err(_) => { storage_a = a.file_name().to_string_lossy(); storage_a.as_ref().into() } }; let b_name = match gix_path::os_str_into_bstr(b.file_name()) { Ok(f) => f, Err(_) => { storage_b = b.file_name().to_string_lossy(); storage_b.as_ref().into() } }; // "common." < "common/" < "common0" let common = a_name.len().min(b_name.len()); a_name[..common].cmp(&b_name[..common]).then_with(|| { let a = a_name.get(common).or_else(|| a.file_type().is_dir().then_some(&b'/')); let b = b_name.get(common).or_else(|| b.file_type().is_dir().then_some(&b'/')); a.cmp(&b) }) }) .into(), precompose_unicode, } } /// The Iterator yielding directory items pub type DirEntryIter = super::walkdir_precompose::WalkDirIter; } #[cfg(feature = "walkdir")] pub use self::walkdir::{WalkDir, walkdir_new, walkdir_sorted_new}; /// Prepare open options which won't follow symlinks when the file is opened. /// /// Note: only effective on unix currently. pub fn open_options_no_follow() -> std::fs::OpenOptions { #[cfg_attr(not(unix), allow(unused_mut))] let mut options = std::fs::OpenOptions::new(); #[cfg(unix)] { /// Make sure that it's impossible to follow through to the target of symlinks. /// Note that this will still follow symlinks in the path, which is what we assume /// has been checked separately. use std::os::unix::fs::OpenOptionsExt; options.custom_flags(libc::O_NOFOLLOW); } options }