blob: 97f40c367f9066f3c9f2919d513e25a0649b2041 [file] [log] [blame] [edit]
//! 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
//! * [`jwalk::WalkDir`](https://docs.rs/jwalk/0.5.1/jwalk/type.WalkDir.html) if `parallel` feature is enabled
//! * [walkdir::WalkDir](https://docs.rs/walkdir/2.3.1/walkdir/struct.WalkDir.html) otherwise
#[cfg(any(feature = "walkdir", feature = "fs-walkdir-parallel"))]
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-walkdir-parallel", feature = "fs-read-dir"))]
mod walkdir_precompose {
use std::borrow::Cow;
use std::ffi::OsStr;
use std::path::Path;
#[derive(Debug)]
pub struct DirEntry<T: std::fmt::Debug> {
inner: T,
precompose_unicode: bool,
}
impl<T: std::fmt::Debug> DirEntry<T> {
/// 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<std::fs::FileType>;
}
impl<T: DirEntryApi + std::fmt::Debug> DirEntry<T> {
/// 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<std::fs::FileType> {
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(any(feature = "walkdir", feature = "fs-walkdir-parallel"))]
pub struct WalkDir<T> {
pub(crate) inner: Option<T>,
pub(crate) precompose_unicode: bool,
}
#[cfg(any(feature = "walkdir", feature = "fs-walkdir-parallel"))]
pub struct WalkDirIter<T, I, E>
where
T: Iterator<Item = Result<I, E>>,
I: DirEntryApi,
{
pub(crate) inner: T,
pub(crate) precompose_unicode: bool,
}
#[cfg(any(feature = "walkdir", feature = "fs-walkdir-parallel"))]
impl<T, I, E> Iterator for WalkDirIter<T, I, E>
where
T: Iterator<Item = Result<I, E>>,
I: DirEntryApi + std::fmt::Debug,
{
type Item = Result<DirEntry<I>, E>;
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.map(|res| res.map(|entry| DirEntry::new(entry, self.precompose_unicode)))
}
}
}
///
#[allow(clippy::empty_docs)]
#[cfg(feature = "fs-read-dir")]
pub mod read_dir {
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fs::FileType;
use std::path::Path;
/// A directory entry adding precompose-unicode support to [`std::fs::DirEntry`].
pub type DirEntry = super::walkdir_precompose::DirEntry<std::fs::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<FileType> {
self.file_type()
}
}
}
///
#[allow(clippy::empty_docs)]
#[cfg(feature = "fs-walkdir-parallel")]
pub mod walkdir {
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fs::FileType;
use std::path::Path;
use jwalk::WalkDir as WalkDirImpl;
pub use jwalk::{DirEntry as DirEntryGeneric, DirEntryIter as DirEntryIterGeneric, Error};
pub use super::shared::Parallelism;
type DirEntryImpl = DirEntryGeneric<((), ())>;
/// A directory entry returned by [DirEntryIter].
pub type DirEntry = super::walkdir_precompose::DirEntry<DirEntryImpl>;
/// A platform to create a [DirEntryIter] from.
pub type WalkDir = super::walkdir_precompose::WalkDir<WalkDirImpl>;
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<FileType> {
Ok(self.file_type())
}
}
impl IntoIterator for WalkDir {
type Item = Result<DirEntry, jwalk::Error>;
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
}
}
impl From<Parallelism> for jwalk::Parallelism {
fn from(v: Parallelism) -> Self {
match v {
Parallelism::Serial => jwalk::Parallelism::Serial,
Parallelism::ThreadPoolPerTraversal { thread_name } => std::thread::available_parallelism()
.map_or_else(
|_| Parallelism::Serial.into(),
|threads| {
let pool = jwalk::rayon::ThreadPoolBuilder::new()
.num_threads(threads.get().min(16))
.stack_size(128 * 1024)
.thread_name(move |idx| format!("{thread_name} {idx}"))
.build()
.expect("we only set options that can't cause a build failure");
jwalk::Parallelism::RayonExistingPool {
pool: pool.into(),
busy_timeout: None,
}
},
),
}
}
}
/// 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: Parallelism, precompose_unicode: bool) -> WalkDir {
WalkDir {
inner: WalkDirImpl::new(root)
.skip_hidden(false)
.parallelism(parallelism.into())
.into(),
precompose_unicode,
}
}
/// Instantiate a new directory iterator which will not skip hidden files and is sorted
///
/// Use `precompose_unicode` to represent the `core.precomposeUnicode` configuration option.
pub fn walkdir_sorted_new(root: &Path, parallelism: Parallelism, precompose_unicode: bool) -> WalkDir {
WalkDir {
inner: WalkDirImpl::new(root)
.skip_hidden(false)
.sort(true)
.parallelism(parallelism.into())
.into(),
precompose_unicode,
}
}
type DirEntryIterImpl = DirEntryIterGeneric<((), ())>;
/// The Iterator yielding directory items
pub type DirEntryIter = super::walkdir_precompose::WalkDirIter<DirEntryIterImpl, DirEntryImpl, jwalk::Error>;
}
///
#[allow(clippy::empty_docs)]
#[cfg(all(feature = "walkdir", not(feature = "fs-walkdir-parallel")))]
pub mod walkdir {
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fs::FileType;
use std::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<DirEntryImpl>;
/// A platform to create a [DirEntryIter] from.
pub type WalkDir = super::walkdir_precompose::WalkDir<WalkDirImpl>;
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<FileType> {
Ok(self.file_type())
}
}
impl IntoIterator for WalkDir {
type Item = Result<DirEntry, walkdir::Error>;
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.
pub fn walkdir_sorted_new(root: &Path, _: Parallelism, precompose_unicode: bool) -> WalkDir {
WalkDir {
inner: WalkDirImpl::new(root).sort_by_file_name().into(),
precompose_unicode,
}
}
/// The Iterator yielding directory items
pub type DirEntryIter = super::walkdir_precompose::WalkDirIter<walkdir::IntoIter, DirEntryImpl, walkdir::Error>;
}
#[cfg(any(feature = "walkdir", feature = "fs-walkdir-parallel"))]
pub use self::walkdir::{walkdir_new, walkdir_sorted_new, WalkDir};
/// 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
}