-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor walk behavior types into a
behavior
module.
This change moves walk behavior types from the `walk` module into a `behavior` sub-module. These types are re-exported from `walk`.
- Loading branch information
1 parent
da774b0
commit e439b14
Showing
2 changed files
with
372 additions
and
369 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,365 @@ | ||
use std::num::NonZeroUsize; | ||
|
||
/// Configuration for a minimum depth of matched files in a walk. | ||
/// | ||
/// Unlike a maximum depth, a minimum depth cannot be zero, because such a minimum has no effect. | ||
/// To configure a minimum depth or else an unbounded depth, use | ||
/// [`DepthMin::from_min_or_unbounded`]. | ||
/// | ||
/// [`DepthMin::from_min_or_unbounded`]: crate::walk::DepthMin::from_min_or_unbounded | ||
#[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
pub struct DepthMin(pub NonZeroUsize); | ||
|
||
impl DepthMin { | ||
/// Constructs a [`DepthBehavior`] with a minimum depth or, if zero, unbounded. | ||
/// | ||
/// # Examples | ||
/// | ||
/// The following example places a minimum bound on the depth of a walk. | ||
/// | ||
/// ```rust,no_run | ||
/// use wax::walk::DepthMin; | ||
/// use wax::Glob; | ||
/// | ||
/// for entry in Glob::new("**") | ||
/// .unwrap() | ||
/// .walk_with_behavior(".", DepthMin::from_min_or_unbounded(1)) | ||
/// { | ||
/// let entry = entry.unwrap(); | ||
/// // ... | ||
/// } | ||
/// ``` | ||
/// | ||
/// [`DepthBehavior`]: crate::walk::DepthBehavior | ||
pub fn from_min_or_unbounded(min: usize) -> DepthBehavior { | ||
use DepthBehavior::{Min, Unbounded}; | ||
|
||
DepthMin::try_from(min).map(Min).unwrap_or(Unbounded) | ||
} | ||
|
||
pub(crate) fn min_at_pivot(self, pivot: usize) -> usize { | ||
self.0.get().saturating_sub(pivot) | ||
} | ||
} | ||
|
||
impl From<NonZeroUsize> for DepthMin { | ||
fn from(min: NonZeroUsize) -> Self { | ||
DepthMin(min) | ||
} | ||
} | ||
|
||
impl From<DepthMin> for NonZeroUsize { | ||
fn from(min: DepthMin) -> Self { | ||
min.0 | ||
} | ||
} | ||
|
||
impl TryFrom<usize> for DepthMin { | ||
type Error = (); | ||
|
||
fn try_from(min: usize) -> Result<Self, Self::Error> { | ||
NonZeroUsize::new(min).map(DepthMin).ok_or(()) | ||
} | ||
} | ||
|
||
/// Configuration for a maximum depth of a walk. | ||
#[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
pub struct DepthMax(pub usize); | ||
|
||
impl DepthMax { | ||
pub(crate) fn max_at_pivot(self, pivot: usize) -> usize { | ||
self.0.saturating_sub(pivot) | ||
} | ||
} | ||
|
||
impl From<usize> for DepthMax { | ||
fn from(max: usize) -> Self { | ||
DepthMax(max) | ||
} | ||
} | ||
|
||
impl From<DepthMax> for usize { | ||
fn from(max: DepthMax) -> Self { | ||
max.0 | ||
} | ||
} | ||
|
||
/// Configuration for minimum and maximum depths of a walk and matched files. | ||
#[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
pub struct DepthMinMax { | ||
pub min: NonZeroUsize, | ||
pub extent: usize, | ||
} | ||
|
||
impl DepthMinMax { | ||
/// Constructs a [`DepthBehavior`] with a maximum depth and, if nonzero, a minimum depth. | ||
/// | ||
/// The depths need not be ordered. | ||
/// | ||
/// # Examples | ||
/// | ||
/// The following example places both a minimum and maximum bound on the depth of a walk. | ||
/// | ||
/// ```rust,no_run | ||
/// use wax::walk::DepthMinMax; | ||
/// use wax::Glob; | ||
/// | ||
/// for entry in Glob::new("**") | ||
/// .unwrap() | ||
/// .walk_with_behavior(".", DepthMinMax::from_depths_or_max(1, 2)) | ||
/// { | ||
/// let entry = entry.unwrap(); | ||
/// // ... | ||
/// } | ||
/// ``` | ||
/// | ||
/// [`DepthBehavior`]: crate::walk::DepthBehavior | ||
pub fn from_depths_or_max(p: usize, q: usize) -> DepthBehavior { | ||
use DepthBehavior::{Max, MinMax}; | ||
|
||
let [min, max] = crate::minmax(p, q); | ||
let extent = max - min; | ||
NonZeroUsize::new(min) | ||
.map(|min| DepthMinMax { min, extent }) | ||
.map_or_else(|| Max(DepthMax(max)), MinMax) | ||
} | ||
|
||
pub(crate) fn min_max_at_pivot(self, pivot: usize) -> (usize, usize) { | ||
( | ||
self.min.get().saturating_sub(pivot), | ||
self.max().get().saturating_sub(pivot), | ||
) | ||
} | ||
|
||
pub fn max(&self) -> NonZeroUsize { | ||
self.min.saturating_add(self.extent) | ||
} | ||
} | ||
|
||
/// Configuration for filtering walks and files by depth. | ||
/// | ||
/// Determines the minimum and maximum depths of a walk and files yielded by that walk relative to | ||
/// the [root path segment][`Entry::root_relative_paths`]. A minimum depth only filters files, but | ||
/// a maximum depth also limits the depth of the walk (directories beneath the maximum are not read | ||
/// from the file system). | ||
/// | ||
/// See [`WalkBehavior`]. | ||
/// | ||
/// # Defaults | ||
/// | ||
/// The default depth behavior is [`Unbounded`]. | ||
/// | ||
/// [`Entry::root_relative_paths`]: crate::walk::Entry::root_relative_paths | ||
/// [`Unbounded`]: crate::walk::DepthBehavior::Unbounded | ||
/// [`WalkBehavior`]: crate::walk::WalkBehavior | ||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] | ||
pub enum DepthBehavior { | ||
#[default] | ||
Unbounded, | ||
Min(DepthMin), | ||
Max(DepthMax), | ||
MinMax(DepthMinMax), | ||
} | ||
|
||
impl DepthBehavior { | ||
// TODO: Provide a similar function for `Glob`s called something like | ||
// `bounded_with_depth_variance`, which additionally accepts a depth variance and | ||
// considers this variance when constructing the `DepthBehavior`. | ||
/// Constructs a bounded `DepthBehavior` from a minimum and/or maximum depth. | ||
/// | ||
/// This function provides an ergonomic way to place bounds on the depth of a walk. At least | ||
/// one closed depth is required. A given depth is closed if `Some` and is open if `None`. Note | ||
/// that a closed depth need not be explicitly wrapped in `Some`, because the depth parameters | ||
/// are `impl Into<Option<usize>>`. | ||
/// | ||
/// Returns `None` if both the minimum and maximum depths are both open (unbounded) or if both | ||
/// depths are closed but are misordered (the minimum is greater than the maximum). Never | ||
/// returns [`Unbounded`]. | ||
/// | ||
/// # Examples | ||
/// | ||
/// The following example places a maximum bound on the depth of a walk by using an open | ||
/// minimum depth (`None`). | ||
/// | ||
/// ```rust,no_run | ||
/// use wax::walk::DepthBehavior; | ||
/// use wax::Glob; | ||
/// | ||
/// for entry in Glob::new("**") | ||
/// .unwrap() | ||
/// .walk_with_behavior(".", DepthBehavior::bounded(None, 2).unwrap()) | ||
/// { | ||
/// let entry = entry.unwrap(); | ||
/// // ... | ||
/// } | ||
/// ``` | ||
/// | ||
/// [`Unbounded`]: crate::walk::DepthBehavior::Unbounded | ||
pub fn bounded(min: impl Into<Option<usize>>, max: impl Into<Option<usize>>) -> Option<Self> { | ||
use DepthBehavior::{Max, Min, MinMax}; | ||
|
||
match (min.into(), max.into()) { | ||
(Some(min), None) => NonZeroUsize::new(min).map(DepthMin).map(Min), | ||
(None, Some(max)) => Some(Max(DepthMax(max))), | ||
(Some(min), Some(max)) if min <= max => NonZeroUsize::new(min) | ||
.map(|min| DepthMinMax { | ||
min, | ||
extent: max - min.get(), | ||
}) | ||
.map(MinMax), | ||
_ => None, | ||
} | ||
} | ||
} | ||
|
||
impl From<DepthMax> for DepthBehavior { | ||
fn from(max: DepthMax) -> Self { | ||
DepthBehavior::Max(max) | ||
} | ||
} | ||
|
||
impl From<DepthMin> for DepthBehavior { | ||
fn from(min: DepthMin) -> Self { | ||
DepthBehavior::Min(min) | ||
} | ||
} | ||
|
||
impl From<DepthMinMax> for DepthBehavior { | ||
fn from(minmax: DepthMinMax) -> Self { | ||
DepthBehavior::MinMax(minmax) | ||
} | ||
} | ||
|
||
/// Configuration for interpreting symbolic links. | ||
/// | ||
/// Determines how symbolic links are interpreted when walking directory trees using functions like | ||
/// [`Glob::walk_with_behavior`]. | ||
/// | ||
/// # Defaults | ||
/// | ||
/// The default link behavior is [`ReadFile`] (links are read as regular files and their targets | ||
/// are ignored). | ||
/// | ||
/// [`Glob::walk_with_behavior`]: crate::Glob::walk_with_behavior | ||
/// [`ReadFile`]: crate::walk::LinkBehavior::ReadFile | ||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] | ||
pub enum LinkBehavior { | ||
/// Read the symbolic link file itself. | ||
/// | ||
/// This behavior reads the symbolic link as a regular file. The corresponding entry uses the | ||
/// path of the link file and its metadata describes the link file itself. The target is | ||
/// effectively ignored and traversal does **not** follow the link. | ||
#[default] | ||
ReadFile, | ||
/// Read the target of the symbolic link. | ||
/// | ||
/// This behavior reads the target of the symbolic link. The corresponding entry uses the path | ||
/// of the link file and its metadata describes the target. If the target is a directory, then | ||
/// traversal follows the link and descend into the target. | ||
/// | ||
/// If a link is re-entrant and forms a cycle, then an error will be emitted instead of an | ||
/// entry and traversal does not follow the link. | ||
ReadTarget, | ||
} | ||
|
||
/// Configuration for walking directory trees. | ||
/// | ||
/// Determines the behavior of the traversal within a directory tree when using functions like | ||
/// [`Glob::walk_with_behavior`]. `WalkBehavior` can be constructed via conversions from types | ||
/// representing its fields and sub-fields. APIs generally accept `impl Into<WalkBehavior>`, so | ||
/// these conversion can be used implicitly. When constructed using such a conversion, | ||
/// `WalkBehavior` will use defaults for any remaining fields. | ||
/// | ||
/// # Defaults | ||
/// | ||
/// By default, walk behavior has [unbounded depth][`DepthBehavior::Unbounded`] and reads links as | ||
/// [regular files][`LinkBehavior::ReadFile`] (ignoring their targets). Fields have the following | ||
/// values: | ||
/// | ||
/// | Field | Description | Value | | ||
/// |-----------|-----------------------------------|------------------------------| | ||
/// | [`depth`] | Bounds on depth. | [`DepthBehavior::Unbounded`] | | ||
/// | [`link`] | Interpretation of symbolic links. | [`LinkBehavior::ReadFile`] | | ||
/// | ||
/// # Examples | ||
/// | ||
/// By default, symbolic links are interpreted as regular files and targets are ignored. To read | ||
/// linked targets, use [`LinkBehavior::ReadTarget`]. | ||
/// | ||
/// ```rust,no_run | ||
/// use wax::walk::LinkBehavior; | ||
/// use wax::Glob; | ||
/// | ||
/// for entry in Glob::new("**") | ||
/// .unwrap() | ||
/// .walk_with_behavior(".", LinkBehavior::ReadTarget) | ||
/// { | ||
/// let entry = entry.unwrap(); | ||
/// // ... | ||
/// } | ||
/// ``` | ||
/// | ||
/// [`depth`]: crate::walk::WalkBehavior::depth | ||
/// [`Glob::walk_with_behavior`]: crate::Glob::walk_with_behavior | ||
/// [`link`]: crate::walk::WalkBehavior::link | ||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] | ||
pub struct WalkBehavior { | ||
/// Bounds on the depth of the walk and matched files. | ||
/// | ||
/// Determines the minimum and maximum depths of a walk and matched files relative to the [root | ||
/// path segment][`Entry::root_relative_paths`]. The default value is | ||
/// [`DepthBehavior::Unbounded`]. | ||
/// | ||
/// [`DepthBehavior::Unbounded`]: crate::walk::DepthBehavior::Unbounded | ||
/// [`Entry::root_relative_paths`]: crate::walk::Entry::root_relative_paths | ||
pub depth: DepthBehavior, | ||
/// Interpretation of symbolic links. | ||
/// | ||
/// Determines how symbolic links are interpreted when walking a directory tree. The default | ||
/// value is [`LinkBehavior::ReadFile`]. | ||
/// | ||
/// [`LinkBehavior::ReadFile`]: crate::walk::LinkBehavior::ReadFile | ||
pub link: LinkBehavior, | ||
} | ||
|
||
impl From<()> for WalkBehavior { | ||
fn from(_: ()) -> Self { | ||
Default::default() | ||
} | ||
} | ||
|
||
impl From<DepthBehavior> for WalkBehavior { | ||
fn from(depth: DepthBehavior) -> Self { | ||
WalkBehavior { | ||
depth, | ||
..Default::default() | ||
} | ||
} | ||
} | ||
|
||
impl From<DepthMax> for WalkBehavior { | ||
fn from(max: DepthMax) -> Self { | ||
DepthBehavior::from(max).into() | ||
} | ||
} | ||
|
||
impl From<DepthMin> for WalkBehavior { | ||
fn from(min: DepthMin) -> Self { | ||
DepthBehavior::from(min).into() | ||
} | ||
} | ||
|
||
impl From<DepthMinMax> for WalkBehavior { | ||
fn from(minmax: DepthMinMax) -> Self { | ||
DepthBehavior::from(minmax).into() | ||
} | ||
} | ||
|
||
impl From<LinkBehavior> for WalkBehavior { | ||
fn from(link: LinkBehavior) -> Self { | ||
WalkBehavior { | ||
link, | ||
..Default::default() | ||
} | ||
} | ||
} |
Oops, something went wrong.