Skip to content

Commit

Permalink
[wip] Fixup APIs and update documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
olson-sean-k committed Mar 15, 2024
1 parent 03d82b9 commit 05b5f04
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 126 deletions.
13 changes: 13 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub mod prelude {
use miette::Diagnostic;
use regex::Regex;
use std::borrow::{Borrow, Cow};
use std::cmp::Ordering;
use std::convert::Infallible;
use std::ffi::OsStr;
use std::fmt::{self, Debug, Display, Formatter};
Expand Down Expand Up @@ -1063,6 +1064,18 @@ fn parse_and_check(
Ok(checked)
}

fn minmax<T>(lhs: T, rhs: T) -> [T; 2]
where
T: Ord,
{
use Ordering::{Equal, Greater, Less};

match lhs.cmp(&rhs) {
Equal | Less => [lhs, rhs],
Greater => [rhs, lhs],
}
}

#[cfg(test)]
pub mod harness {
use expect_macro::expect;
Expand Down
3 changes: 0 additions & 3 deletions src/token/variance/bound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,6 @@ impl From<VariantRange> for NaturalRange {
}
}

// NOTE: Given the naturals X and Y where X < Y, this defines an unconventional meaning for the
// range [Y,X] and repetitions like `<_:10,1>`: the bounds are reordered, so `<_:10,1>` and
// `<_:1,10>` are the same.
impl<T> From<(usize, T)> for NaturalRange
where
T: Into<Option<usize>>,
Expand Down
15 changes: 1 addition & 14 deletions src/token/variance/invariant/natural.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::cmp::Ordering;
use std::num::NonZeroUsize;

use crate::token::variance::bound::{
Expand Down Expand Up @@ -80,7 +79,7 @@ macro_rules! impl_invariant_natural {
}

fn bound(lhs: Self, rhs: Self) -> Boundedness<Self::Bound> {
let (lower, upper) = self::minmax(lhs, rhs);
let [lower, upper] = crate::minmax(lhs, rhs);
BoundedVariantRange::try_from_lower_and_upper(lower.0, upper.0)
.map_or(Unbounded, Bounded)
}
Expand Down Expand Up @@ -144,15 +143,3 @@ impl GlobVariance<Depth> {
!self.has_upper_bound()
}
}

fn minmax<T>(lhs: T, rhs: T) -> (T, T)
where
T: Ord,
{
use Ordering::{Equal, Greater, Less};

match lhs.cmp(&rhs) {
Equal | Less => (lhs, rhs),
Greater => (rhs, lhs),
}
}
93 changes: 44 additions & 49 deletions src/walk/glob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,18 @@ impl<'t> Glob<'t> {
/// directory trees.
///
/// As with [`Path::join`] and [`PathBuf::push`], the base directory can be escaped or
/// overridden by rooted `Glob`s. In many cases, the current working directory `.` is an
/// appropriate base directory and will be intuitively ignored if the `Glob` is rooted, such as
/// in `/mnt/media/**/*.mp4`. The [`has_root`] function can be used to check if a `Glob` is
/// rooted.
///
/// The root directory is either the given directory or, if rooted, the [invariant
/// prefix][`Glob::partition`] of the `Glob`. Either way, this function joins the given
/// directory with any invariant prefix to potentially begin the walk as far down the tree as
/// possible. **The prefix and any [semantic literals][`Glob::has_semantic_literals`] in this
/// prefix are interpreted semantically as a path**, so components like `.` and `..` that
/// precede variant patterns interact with the base directory semantically. This means that
/// expressions like `../**` escape the base directory as expected on Unix and Windows, for
/// example. To query the root directory of the walk, see [`Glob::walker`].
/// overridden by [a `Glob` that has a root][`has_root`]. In many cases, the current working
/// directory `.` is an appropriate base directory and will be intuitively ignored if the
/// `Glob` is rooted, such as in `/mnt/media/**/*.mp4`.
///
/// The [root path segment][`Entry::root_relative_paths`] is either the given directory or, if
/// the `Glob` has a root, the [invariant prefix][`Glob::partition`] of the `Glob`. Either way,
/// this function joins the given directory with any invariant prefix in the `Glob` to
/// potentially begin the walk as far down the tree as possible. **The prefix and any [semantic
/// literals][`Glob::has_semantic_literals`] in this prefix are interpreted semantically as a
/// path**, so components like `.` and `..` that precede variant patterns interact with the
/// base directory semantically. This means that expressions like `../**` escape the base
/// directory as expected on Unix and Windows, for example.
///
/// This function uses the default [`WalkBehavior`]. To configure the behavior of the
/// traversal, see [`Glob::walk_with_behavior`].
Expand All @@ -60,9 +59,9 @@ impl<'t> Glob<'t> {
/// ```
///
/// Glob expressions do not support general negations, but the [`not`] combinator can be used
/// when walking a directory tree to filter entries using patterns. **This should generally be
/// preferred over functions like [`Iterator::filter`], because it avoids unnecessary reads of
/// directory trees when matching [exhaustive negations][`Program::is_exhaustive`].**
/// when walking a directory tree to filter entries using patterns. **Prefer this over
/// functions like [`Iterator::filter`], because it avoids unnecessary reads of directory trees
/// when matching [exhaustive negations][`Program::is_exhaustive`].**
///
/// ```rust,no_run
/// use wax::walk::{Entry, FileIterator};
Expand All @@ -80,8 +79,8 @@ impl<'t> Glob<'t> {
/// ```
///
/// [`Any`]: crate::Any
/// [`Entry::root_relative_paths`]: crate::walk::Entry::root_relative_paths
/// [`Glob::walk_with_behavior`]: crate::Glob::walk_with_behavior
/// [`Glob::walker`]: crate::Glob::walker
/// [`GlobEntry`]: crate::walk::GlobEntry
/// [`has_root`]: crate::Glob::has_root
/// [`FileIterator`]: crate::walk::FileIterator
Expand All @@ -92,21 +91,17 @@ impl<'t> Glob<'t> {
/// [`Program`]: crate::Program
/// [`Program::is_exhaustive`]: crate::Program::is_exhaustive
/// [`WalkBehavior`]: crate::walk::WalkBehavior
pub fn walk(
&self,
directory: impl Into<PathBuf>,
) -> impl 'static + FileIterator<Entry = GlobEntry> {
self.walk_with_behavior(directory, WalkBehavior::default())
pub fn walk(&self, path: impl Into<PathBuf>) -> impl 'static + FileIterator<Entry = GlobEntry> {
self.walk_with_behavior(path, WalkBehavior::default())
}

/// Gets an iterator over matching files in a directory tree.
///
/// This function is the same as [`Glob::walk`], but it additionally accepts a [`WalkBehavior`]
/// that configures how the traversal interacts with symbolic links, the maximum depth from the
/// root, etc.
/// that configures how the traversal interacts with symbolic links, bounds on depth, etc.
///
/// Depth is relative to the root directory of the traversal, which is determined by joining
/// the given path and any [invariant prefix][`Glob::partition`] of the `Glob`.
/// Depth is bounded relative to [the root path segment][`Entry::root_relative_paths`]
/// of the traversal.
///
/// See [`Glob::walk`] for more information.
///
Expand Down Expand Up @@ -138,30 +133,31 @@ impl<'t> Glob<'t> {
/// }
/// ```
///
/// [`Glob::partition`]: crate::Glob::partition
/// [`Entry::root_relative_paths`]: crate::walk::Entry::root_relative_paths
/// [`Glob::walk`]: crate::Glob::walk
/// [`LinkBehavior`]: crate::walk::LinkBehavior
/// [`WalkBehavior`]: crate::walk::WalkBehavior
pub fn walk_with_behavior(
&self,
directory: impl Into<PathBuf>,
path: impl Into<PathBuf>,
behavior: impl Into<WalkBehavior>,
) -> impl 'static + FileIterator<Entry = GlobEntry> {
GlobWalker {
anchor: self.anchor(directory),
anchor: self.anchor(path),
program: WalkProgram {
complete: self.program.clone(),
// Do not compile component programs for empty globs.
//
// An empty glob consists solely of an empty literal token and only matches empty text
// (""). A walk program compiled from such a glob has an empty component pattern and
// matches nothing. This means that walking an empty glob never yields any paths. At
// first blush, this seems consistent with an empty glob. However, walking conceptually
// matches a glob against the subtrees in a path and there is arguably an implicit
// empty tree. This is also more composable when partitioning and (re)building paths.
// An empty glob consists solely of an empty literal token and only matches empty
// text (""). A walk program compiled from such a glob has an empty component
// pattern and matches nothing. This means that walking an empty glob never yields
// any paths. At first blush, this seems consistent with an empty glob. However,
// walking conceptually matches a glob against the sub-trees in a path and there is
// arguably an implicit empty tree. This is also more composable when partitioning
// and (re)building paths.
//
// The result is that matching an empty glob against the path `foo` yields `foo` and
// only `foo` (assuming that the path exists).
// The result is that matching an empty glob against the path `foo` yields `foo`
// and only `foo` (assuming that the path exists).
components: if self.is_empty() {
vec![]
}
Expand All @@ -174,8 +170,8 @@ impl<'t> Glob<'t> {
.walk_with_behavior(behavior)
}

fn anchor(&self, directory: impl Into<PathBuf>) -> Anchor {
let directory = directory.into();
fn anchor(&self, path: impl Into<PathBuf>) -> Anchor {
let path = path.into();
let prefix: Option<PathBuf> = {
let (_, prefix) = self.tree.as_ref().as_token().invariant_text_prefix();
if prefix.is_empty() {
Expand All @@ -199,19 +195,18 @@ impl<'t> Glob<'t> {
// zero), as the entire root path is present in the glob expression and the given directory
// is completely discarded.
let (root, pivot) = match prefix {
Some(prefix) => directory.join_and_get_depth(prefix),
_ => (directory, 0),
Some(prefix) => path.join_and_get_depth(prefix),
_ => (path, 0),
};
Anchor { root, pivot }
}
}

/// Root path and pivot of a `Glob` when walking a particular directory.
/// Root path and pivot of a `Glob` when walking a particular target path.
///
/// For unrooted globs, the pivot can be used to isolate the target directory given to walk
/// functions like `Glob::walk`. This is necessary to implement `Entry` and for interpreting depth
/// behavior, which is always relative to the target directory (and ignores any invariant prefix in
/// a glob).
/// For unrooted globs, the pivot can be used to isolate the target path given to walk functions
/// like `Glob::walk`. This is necessary to implement `Entry` and for interpreting depth behavior,
/// which is always relative to the target path (and ignores any invariant prefix in a glob).
#[derive(Clone, Debug)]
struct Anchor {
/// The root path of the walk.
Expand All @@ -221,9 +216,9 @@ struct Anchor {
/// The number of components from the end of `root` that are present in any invariant prefix of
/// the glob expression.
///
/// The pivot partitions the root path into the target directory and any invariant prefix in
/// the `Glob` (this prefix becomes a postfix in the root path or, when rooted, replaces any
/// target directory).
/// The pivot partitions the root path into the target path and any invariant prefix in the
/// `Glob` (this prefix becomes a postfix in the root path or, when rooted, replaces any target
/// path).
pivot: usize,
}

Expand Down
Loading

0 comments on commit 05b5f04

Please sign in to comment.