From 6280daf45a1283081aedf2a086ae92796bca7911 Mon Sep 17 00:00:00 2001 From: Sean Olson Date: Mon, 18 Mar 2024 15:08:48 -0700 Subject: [PATCH] Update documentation for `Program` and `query` types. --- src/lib.rs | 81 ++++++++++++--- src/query.rs | 210 +++++++++++++++++++------------------- src/token/variance/mod.rs | 1 + 3 files changed, 169 insertions(+), 123 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d3c3985..93894e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,11 +118,33 @@ impl StrExt for str { /// A representation of a glob expression. /// /// This trait is implemented by types that can be converted into a [`Program`], such as `str` -/// slices and, of course, [`Program`] types like [`Glob`]. +/// slices and compiled [`Program`] types like [`Glob`]. APIs that accept patterns typically do so +/// via this trait. /// -/// [`Result`] types also implement this trait when the okay type is a [`Program`] and the error -/// type is [`BuildError`]. This means that APIs that accept a `Pattern` can also accept the result -/// of constructing a [`Program`] without the need to explicitly inspect the inner result first. +/// # Examples +/// +/// [`Result`] types also implement this trait when the success type is a [`Program`] and the error +/// type is [`BuildError`]. This means that APIs that accept a `Pattern` can also accept the +/// intermediate result of constructing a [`Program`] without the need to explicitly inspect the +/// inner result first (error handling can be deferred). +/// +/// ```rust +/// use wax::{Glob, Program}; +/// +/// # fn fallible() -> Result<(), wax::BuildError> { +/// // The inner results of `any` are not checked via `?` and can be passed directly to the +/// // outermost `any` call. This is true of all APIs that accept `Pattern`. +/// #[rustfmt::skip] +/// let any = wax::any([ +/// wax::any([Glob::new("**/*.txt")]), +/// wax::any([ +/// "**/*.pdf", +/// "**/*.tex", +/// ]), +/// ])?; // Defer error handling until the here. +/// # Ok(()) +/// # } +/// ``` /// /// [`BuildError`]: crate::BuildError /// [`Glob`]: crate::Glob @@ -150,44 +172,69 @@ impl<'t> Pattern<'t> for &'t str { /// A compiled [`Pattern`] that can be inspected and matched against paths. /// -/// Matching is a logical operation and does **not** interact with a file system. To handle path -/// operations, use [`Path`] and/or [`PathBuf`] and their associated functions. See -/// [`Glob::partition`] and `Glob::walk` for more about globs and path operations. -/// /// [`Glob::partition`]: crate::Glob::partition /// [`Path`]: std::path::Path /// [`PathBuf`]: std::path::PathBuf /// [`Pattern`]: crate::Pattern pub trait Program<'t>: Pattern<'t, Error = Infallible> { - /// Returns `true` if a path matches the pattern. + /// Returns `true` if the [candidate path][`CandidatePath`] matches the pattern. /// - /// The given path must be convertible into a [`CandidatePath`]. + /// This is a logical operation and does **not** interact with the file system. /// /// [`CandidatePath`]: crate::CandidatePath fn is_match<'p>(&self, path: impl Into>) -> bool; - /// Gets [matched text][`MatchedText`] in a [`CandidatePath`]. + /// Gets the [matched text][`MatchedText`] in a [`CandidatePath`], if any. /// - /// Returns `None` if the [`CandidatePath`] does not match the pattern. + /// Returns `None` if the [`CandidatePath`] does not match the pattern. This is a logical + /// operation and does **not** interact with the file system. /// /// [`CandidatePath`]: crate::CandidatePath /// [`MatchedText`]: crate::MatchedText fn matched<'p>(&self, path: &'p CandidatePath<'_>) -> Option>; + /// Gets the depth variance of the pattern. fn depth(&self) -> DepthVariance; + /// Gets the text variance of the pattern. + /// + /// # Examples + /// + /// Text variance can be used to determine if a pattern can be trivially represented by an + /// equivalent path using platform file system APIs. + /// + /// ```rust + /// use std::path::Path; + /// use wax::{Glob, Program}; + /// + /// let glob = Glob::new("/home/user").unwrap(); + /// let text = glob.text(); + /// let path = text.as_path(); + /// + /// assert_eq!(path, Some(Path::new("/home/user"))); + /// ``` fn text(&self) -> TextVariance<'t>; - /// Returns `true` if the glob has a root. + /// Describes when the pattern matches candidate paths with a root. + /// + /// A glob expression that begins with a separator `/` has a root, but less trivial patterns + /// like `/**` and `` can also root an expression. Some `Program` types may have + /// indeterminate roots and may match both candidate paths with and without a root. In this + /// case, this functions returns [`Sometimes`] (indeterminate). /// - /// As with Unix paths, a glob expression has a root if it begins with a separator `/`. - /// Patterns other than separators may also root an expression, such as `/**` or ``. + /// [`Sometimes`]: crate::query::When::Sometimes fn has_root(&self) -> When; /// Returns `true` if the pattern is exhaustive. /// - /// A glob expression is exhaustive if its terminating component matches any and all sub-trees, - /// such as in the expressions `/home/**` and `local/</>*`. + /// A glob expression is exhaustive if, given a matched candidate path, it necessarily matches + /// any and all sub-trees of that path. For example, glob expressions that end with a tree + /// wildcard like `.local/**` are exhaustive, but so are less trivial expressions like `</>` + /// and `>`. This can be an important property when determining what directory + /// trees to read or which files and directories to select with a pattern. + /// + /// Note that this applies to the **pattern** and **not** to a particular match. It is possible + /// for a particular match against a **non**exhaustive pattern to be exhaustive. fn is_exhaustive(&self) -> bool; } diff --git a/src/query.rs b/src/query.rs index bc2483c..bb147aa 100644 --- a/src/query.rs +++ b/src/query.rs @@ -2,7 +2,14 @@ //! //! [`Program`]: crate::Program +// This module proxies variance and bounds types from the `token` module through types defined here +// via conversions. For example, `VariantRange` has a public counterpart here that lacks many APIs +// that are unnecessary for downstream code. Types in this module generally present less complex +// APIs and insulate their more complex counterparts from the burdens of public export. + use std::borrow::Cow; +use std::num::NonZeroUsize; +use std::path::{Path, PathBuf}; use crate::token::{self, Depth, Text, TokenVariance}; @@ -11,20 +18,20 @@ pub use crate::token::Boundedness; pub use Boundedness::{Bounded, Unbounded}; -pub type DepthVariance = Variance; -pub type TextVariance<'t> = Variance, ()>; - +/// A range over naturals that is necessarily variant (not converged). pub type VariantRange = Boundedness; impl VariantRange { - pub fn lower(&self) -> Boundedness { + /// Gets the lower bound of the range. + pub fn lower(&self) -> Boundedness { match self { Bounded(ref range) => range.lower(), _ => Unbounded, } } - pub fn upper(&self) -> Boundedness { + /// Gets the upper bound of the range. + pub fn upper(&self) -> Boundedness { match self { Bounded(ref range) => range.upper(), _ => Unbounded, @@ -38,18 +45,21 @@ impl From for VariantRange { } } +/// A range over naturals that is necessarily bounded and variant (not converged). #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct BoundedVariantRange { - lower: Boundedness, - upper: Boundedness, + lower: Boundedness, + upper: Boundedness, } impl BoundedVariantRange { - pub fn lower(&self) -> Boundedness { + /// Gets the lower bound of the range. + pub fn lower(&self) -> Boundedness { self.lower } - pub fn upper(&self) -> Boundedness { + /// Gets the lower bound of the range. + pub fn upper(&self) -> Boundedness { self.upper } } @@ -57,15 +67,30 @@ impl BoundedVariantRange { impl From for BoundedVariantRange { fn from(range: token::BoundedVariantRange) -> Self { BoundedVariantRange { - lower: range.lower().into_bound().map_bounded(From::from), - upper: range.upper().into_bound().map_bounded(From::from), + lower: range.lower().into_bound(), + upper: range.upper().into_bound(), } } } +/// The variance of a [`Program`] with respect to some quantity `T`. +/// +/// Variance describes how a quantity may or may not vary in a [`Program`]. When invariant, the +/// quantity has a specific determinant value for any and all matched paths. When variant, the +/// quantity may vary in some way and may or may not be determinant and bounded. +/// +/// See [`DepthVariance`] and [`TextVariance`]. +/// +/// [`Program`]: crate::Program #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Variance { + /// The quantity `T` is invariant. Invariant(T), + /// The quantity `T` is variant with the bounds `B`. + /// + /// Some quantities do not express bounds when variant (i.e., [text][`TextVariance`]). + /// + /// [`TextVariance`]: crate::query::TextVariance Variant(B), } @@ -84,6 +109,9 @@ impl Variance { } } + /// Converts from `&Variance` to `Variance<&T, &B>`. + /// + /// Produces a new `Variance` that contains a reference `self`, leaving `self` in place. pub fn as_ref(&self) -> Variance<&T, &B> { match self { Variance::Invariant(ref invariance) => Variance::Invariant(invariance), @@ -91,23 +119,27 @@ impl Variance { } } + /// Returns `true` if **in**variant. pub fn is_invariant(&self) -> bool { matches!(self, Variance::Invariant(_)) } + /// Returns `true` if variant. pub fn is_variant(&self) -> bool { matches!(self, Variance::Variant(_)) } } -impl From for (Option, Option) { - fn from(depth: DepthVariance) -> Self { - match depth { - DepthVariance::Invariant(depth) => (Some(depth), Some(depth)), - DepthVariance::Variant(bounds) => (bounds.lower().bounded(), bounds.upper().bounded()), - } - } -} +/// Depth variance of a [`Program`]. +/// +/// Depth describes the number of levels into a directory tree from some root that a path +/// represents and a [`Program`] may match. When variant, the bounds of depth are described by +/// [`VariantRange`]. For example, the glob expression `{server/log/,client/**/}*.log` has variant +/// depth of one or more (no upper bound). +/// +/// [`Program`]: crate::Program +/// [`VariantRange`]: crate::query::VariantRange +pub type DepthVariance = Variance; impl From> for DepthVariance { fn from(depth: TokenVariance) -> Self { @@ -118,12 +150,35 @@ impl From> for DepthVariance { } } -impl<'t> From> for Option> { - fn from(text: TextVariance<'t>) -> Self { - match text { - TextVariance::Invariant(text) => Some(text), - _ => None, - } +/// Text variance of a [`Program`]. +/// +/// Text describes the path text that a [`Program`] may match. When its text is invariant, a +/// [`Program`] resolves to a file no differently than a native path using platform file system +/// APIs. For example, the glob expression `{log,log}` has the invariant text `log` and so behaves +/// just like the native path `log`. +/// +/// Text does not describe any bounds when variant. +/// +/// [`Program`]: crate::Program +pub type TextVariance<'t> = Variance, ()>; + +impl<'t> TextVariance<'t> { + /// Converts the text variance into a [`PathBuf`]. + /// + /// Returns `None` if variant. + /// + /// [`PathBuf`]: std::path::PathBuf + pub fn into_path_buf(self) -> Option { + self.invariant().map(Cow::into_owned).map(From::from) + } + + /// Gets a [`Path`] from the invariant text. + /// + /// Returns `None` if variant. + /// + /// [`Path`]: std::path::Path + pub fn as_path(&self) -> Option<&Path> { + self.as_ref().invariant().map(|text| text.as_ref().as_ref()) } } @@ -136,14 +191,22 @@ impl<'t> From>> for TextVariance<'t> { } } +/// Trivalent logic truth value. +/// +/// `When` extends the bivalent Boolean logic of `bool` with a third truth value that represents +/// unknown, uncertainty, etc. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum When { + /// Verum truth value for which a proposition is true or always occurs (i.e., `true`). Always, + /// Indeterminant truth value for which a proposition is unknown or may sometimes occur. Sometimes, + /// Falsum truth value for which a proposition is false or never occurs (i.e., `false`). Never, } impl When { + /// Trivalent logical conjunction (AND) operator. pub fn and(self, other: Self) -> Self { use When::{Always, Never, Sometimes}; @@ -154,6 +217,7 @@ impl When { } } + /// Trivalent logical disjunction (OR) operator. pub fn or(self, other: Self) -> Self { use When::{Always, Never, Sometimes}; @@ -164,6 +228,12 @@ impl When { } } + /// Trivalent logical certainty conjunction operator. + /// + /// This operator is similar to the bivalent conjunction, but is [`Sometimes`] if any operand + /// is [`Sometimes`], preserving a notion of uncertainty in the conjunction. + /// + /// [`Sometimes`]: crate::query::When::Sometimes pub fn certainty(self, other: Self) -> Self { use When::{Always, Never, Sometimes}; @@ -174,14 +244,23 @@ impl When { } } + /// Returns `true` if the truth value is [`Always`] (true). + /// + /// [`Always`]: crate::query::When::Always pub fn is_always(&self) -> bool { matches!(self, When::Always) } + /// Returns `true` if the truth value is [`Sometimes`]. + /// + /// [`Sometimes`]: crate::query::When::Sometimes pub fn is_sometimes(&self) -> bool { matches!(self, When::Sometimes) } + /// Returns `true` if the truth value is [`Never`] (false). + /// + /// [`Never`]: crate::query::When::Never pub fn is_never(&self) -> bool { matches!(self, When::Never) } @@ -261,84 +340,3 @@ impl CapturingToken { self.span } } - -// TODO: Borrow from these documentation comments to document `Variance` and friends. -// // This type is similar to `token::Variance>`, but is simplified for the public -// // API. Invariant text is always expressed as a path and no variant bounds are provided. -// /// Variance of a [`Program`]. -// /// -// /// The variance of a pattern describes the kinds of paths it can match with respect to the -// /// platform file system APIs. [`Program`]s are either variant or invariant. -// /// -// /// An invariant [`Program`] can be represented and completely described by an equivalent path -// /// using the platform's file system APIs. For example, the glob expression `path/to/file.txt` -// /// resolves identically to the paths `path/to/file.txt` and `path\to\file.txt` on Unix and -// /// Windows, respectively. -// /// -// /// A variant [`Program`] resolves differently than any particular path used with the platform's -// /// file system APIs. Such an expression cannot be represented by a single path. This is typically -// /// because the expression matches multiple texts using a regular pattern, such as in the glob -// /// expression `**/*.rs`. -// /// -// /// [`Program`]: crate::Program -// /// [`Variance`]: crate::Variance -// #[derive(Clone, Debug, Eq, Hash, PartialEq)] -// pub enum Variance { -// /// A [`Program`] is invariant and equivalent to a path. -// /// -// /// Some non-literal expressions may be invariant, such as in the expression -// /// `path/[t][o]/{file,file}.txt`, which is invariant on Unix (but not on Windows, because the -// /// character class expressions do not match with case folding). -// /// -// /// [`Program`]: crate::Program -// Invariant( -// /// An equivalent path that completely describes the invariant [`Program`] with respect to -// /// platform file system APIs. -// /// -// /// [`Program`]: crate::Program -// PathBuf, -// ), -// /// A [`Program`] is variant and cannot be completely described by a path. -// /// -// /// Variant expressions may be formed from literals or other **seemingly** invariant -// /// expressions. For example, the variance of literals considers the case sensitivity of the -// /// platform's file system APIs, so the expression `(?i)path/to/file.txt` is variant on Unix -// /// but not on Windows. Similarly, the expression `path/[t][o]/file.txt` is variant on Windows -// /// but not on Unix. -// /// -// /// [`Program`]: crate::Program -// Variant, -// } -// -// impl Variance { -// /// Gets the equivalent native path if invariant. -// /// -// /// Returns `None` if variant. -// pub fn path(&self) -> Option<&Path> { -// match self { -// Variance::Invariant(ref path) => Some(path), -// Variance::Variant => None, -// } -// } -// -// /// Returns `true` if invariant. -// pub fn is_invariant(&self) -> bool { -// matches!(self, Variance::Invariant(_)) -// } -// -// /// Returns `true` if variant. -// pub fn is_variant(&self) -> bool { -// matches!(self, Variance::Variant) -// } -// } -// -// impl From>> for Variance { -// fn from(variance: token::Variance>) -> Self { -// match variance { -// token::Variance::Invariant(text) => { -// Variance::Invariant(PathBuf::from(text.to_string().into_owned())) -// }, -// token::Variance::Variant(_) => Variance::Variant, -// } -// } -// } diff --git a/src/token/variance/mod.rs b/src/token/variance/mod.rs index 80c07f1..2f1fe55 100644 --- a/src/token/variance/mod.rs +++ b/src/token/variance/mod.rs @@ -491,6 +491,7 @@ mod tests { #[case("a/**/b", harness::range(2, None))] #[case("a/**/b/**/c", harness::range(3, None))] #[case("*", harness::range(1, None))] + #[case("{a/b/,c/**/}*.ext", harness::range(1, None))] #[case("**", Variance::unbounded())] #[case("<*/>*", Variance::unbounded())] #[case("</>*", Variance::unbounded())]