diff --git a/Cargo.lock b/Cargo.lock index 4893ba0..efd036f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,9 +229,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "syn" @@ -246,18 +246,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 9bd1b8d..1f5a5e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,6 @@ pedantic = { level = "warn", priority = -1 } [workspace.dependencies] zrx-diagnostic = { version = "0.0.1", path = "crates/zrx-diagnostic" } -zrx-event = { version = "0.0.1", path = "crates/zrx-event" } zrx-executor = { version = "0.0.3", path = "crates/zrx-executor" } zrx-graph = { version = "0.0.10", path = "crates/zrx-graph" } zrx-id = { version = "0.0.11", path = "crates/zrx-id" } @@ -54,7 +53,7 @@ file-id = "0.2.3" globset = "0.4.18" notify = "8.2.0" percent-encoding = "2.3.2" -slab = "0.4.11" -thiserror = "2.0.17" +slab = "0.4.12" +thiserror = "2.0.18" tracing = "0.1.41" walkdir = "2.5.0" diff --git a/crates/zrx-graph/src/graph/topology.rs b/crates/zrx-graph/src/graph/topology.rs index a3683b7..2ca19f0 100644 --- a/crates/zrx-graph/src/graph/topology.rs +++ b/crates/zrx-graph/src/graph/topology.rs @@ -169,7 +169,7 @@ impl PartialEq for Topology { /// builder.add_edge(a, b)?; /// builder.add_edge(b, c)?; /// - /// // Create topology + /// // Create and compare topologies /// let topology = Topology::new(builder.len(), builder.edges()); /// assert_eq!(topology, topology.clone()); /// # Ok(()) diff --git a/crates/zrx-id/src/id/filter.rs b/crates/zrx-id/src/id/filter.rs index 5f53c6a..be647ae 100644 --- a/crates/zrx-id/src/id/filter.rs +++ b/crates/zrx-id/src/id/filter.rs @@ -71,7 +71,7 @@ pub use terms::Terms; /// /// // Create filter builder and insert expression /// let mut builder = Filter::builder(); -/// builder.insert(Expression::any(|expr| { +/// builder.insert(Expression::all(|expr| { /// expr.with(selector!(location = "**/*.md")?)? /// .with(selector!(provider = "file")?) /// })?); diff --git a/crates/zrx-id/src/id/filter/builder.rs b/crates/zrx-id/src/id/filter/builder.rs index 4a3c468..79d385c 100644 --- a/crates/zrx-id/src/id/filter/builder.rs +++ b/crates/zrx-id/src/id/filter/builder.rs @@ -46,7 +46,7 @@ use super::Filter; /// after all modifications were made. #[derive(Debug, Default)] pub struct Builder { - /// Conditions. + /// Condition set. conditions: Slab, } @@ -119,8 +119,8 @@ impl Builder { /// // Create filter builder and insert expression /// let mut builder = Filter::builder(); /// builder.insert(Expression::any(|expr| { - /// expr.with(selector!(location = "**/*.png")?)? - /// .with(selector!(location = "**/*.jpg")?) + /// expr.with(selector!(location = "**/*.jpg")?)? + /// .with(selector!(location = "**/*.png")?) /// })?); /// # Ok(()) /// # } @@ -146,8 +146,8 @@ impl Builder { /// // Create filter builder and insert expression /// let mut builder = Filter::builder(); /// builder.insert(Expression::any(|expr| { - /// expr.with(selector!(location = "**/*.png")?)? - /// .with(selector!(location = "**/*.jpg")?) + /// expr.with(selector!(location = "**/*.jpg")?)? + /// .with(selector!(location = "**/*.png")?) /// })?); /// /// // Remove expression @@ -178,8 +178,8 @@ impl Builder { /// // Create filter builder and insert expression /// let mut builder = Filter::builder(); /// builder.insert(Expression::any(|expr| { - /// expr.with(selector!(location = "**/*.png")?)? - /// .with(selector!(location = "**/*.jpg")?) + /// expr.with(selector!(location = "**/*.jpg")?)? + /// .with(selector!(location = "**/*.png")?) /// })?); /// /// // Create filter from builder @@ -187,7 +187,6 @@ impl Builder { /// # Ok(()) /// # } /// ``` - #[allow(clippy::cast_possible_truncation)] pub fn build(self) -> Result { let mut builder = Matcher::builder(); @@ -198,7 +197,7 @@ impl Builder { // Add all terms of each condition to the mapping and matcher for (index, condition) in &self.conditions { for term in condition.terms() { - mapping.push(index as u32); + mapping.push(u32::try_from(index)?); match term { Term::Id(id) => builder.add(id)?, Term::Selector(selector) => builder.add(selector)?, @@ -209,7 +208,7 @@ impl Builder { // to the list of negations, so it's always checked when matching let mut iter = condition.instructions().iter(); if iter.any(|instruction| instruction.operator() == Operator::Not) { - negations.push(index as u32); + negations.push(u32::try_from(index)?); } } diff --git a/crates/zrx-id/src/id/filter/candidates.rs b/crates/zrx-id/src/id/filter/candidates.rs index bee55d0..9f995d9 100644 --- a/crates/zrx-id/src/id/filter/candidates.rs +++ b/crates/zrx-id/src/id/filter/candidates.rs @@ -81,7 +81,7 @@ impl Filter { /// /// // Create filter builder and insert expression /// let mut builder = Filter::builder(); - /// builder.insert(Expression::any(|expr| { + /// builder.insert(Expression::all(|expr| { /// expr.with(selector!(location = "**/*.md")?)? /// .with(selector!(provider = "file")?) /// })?); @@ -200,13 +200,13 @@ mod tests { fn handles_any() -> Result { let mut builder = Filter::builder(); let _ = builder.insert(Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?); let filter = builder.build()?; for (id, check) in [ - ("zri:file:::docs:image.png:", vec![0]), ("zri:file:::docs:image.jpg:", vec![0]), + ("zri:file:::docs:image.png:", vec![0]), ("zri:file:::docs:image.gif:", vec![]), ] { assert_eq!( @@ -242,14 +242,14 @@ mod tests { fn handles_not() -> Result { let mut builder = Filter::builder(); let _ = builder.insert(Expression::not(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?); let filter = builder.build()?; for (id, check) in [ ("zri:file:::docs:index.md:", vec![0]), - ("zri:file:::docs:image.png:", vec![]), ("zri:file:::docs:image.jpg:", vec![]), + ("zri:file:::docs:image.png:", vec![]), ] { assert_eq!( filter.candidates(&id)?.collect::>(), // fmt @@ -265,18 +265,18 @@ mod tests { let _ = builder.insert(Expression::all(|expr| { expr.with(selector!(provider = "file")?)? .with(Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })) })?); let filter = builder.build()?; for (id, check) in [ ("zri:file:::docs:index.md:", vec![]), - ("zri:file:::docs:image.png:", vec![0]), ("zri:file:::docs:image.jpg:", vec![0]), + ("zri:file:::docs:image.png:", vec![0]), ("zri:file:::docs:image.gif:", vec![]), - ("zri:git:::docs:image.png:", vec![]), ("zri:git:::docs:image.jpg:", vec![]), + ("zri:git:::docs:image.png:", vec![]), ] { assert_eq!( filter.candidates(&id)?.collect::>(), // fmt @@ -294,8 +294,8 @@ mod tests { .with(Expression::any(|expr| { expr.with(selector!(context = "docs")?)? // fmt .with(Expression::not(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) }), ) })) @@ -303,11 +303,11 @@ mod tests { let filter = builder.build()?; for (id, check) in [ ("zri:file:::docs:index.md:", vec![0]), - ("zri:file:::docs:image.png:", vec![0]), ("zri:file:::docs:image.jpg:", vec![0]), + ("zri:file:::docs:image.png:", vec![0]), ("zri:file:::docs:image.gif:", vec![0]), - ("zri:git:::docs:image.png:", vec![]), ("zri:git:::docs:image.jpg:", vec![]), + ("zri:git:::docs:image.png:", vec![]), ] { assert_eq!( filter.candidates(&id)?.collect::>(), // fmt diff --git a/crates/zrx-id/src/id/filter/condition.rs b/crates/zrx-id/src/id/filter/condition.rs index 7dbcef6..ea93681 100644 --- a/crates/zrx-id/src/id/filter/condition.rs +++ b/crates/zrx-id/src/id/filter/condition.rs @@ -142,8 +142,8 @@ mod tests { #[test] fn handles_any() -> Result { let expr = Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?; let condition = Condition::builder(expr).build(); for (matches, check) in [ @@ -162,8 +162,8 @@ mod tests { #[test] fn handles_any_optimized() -> Result { let expr = Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?; let condition = Condition::builder(expr).optimize().build(); for (matches, check) in [ @@ -222,8 +222,8 @@ mod tests { #[test] fn handles_not() -> Result { let expr = Expression::not(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?; let condition = Condition::builder(expr).build(); for (matches, check) in [ @@ -242,8 +242,8 @@ mod tests { #[test] fn handles_not_optimized() -> Result { let expr = Expression::not(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?; let condition = Condition::builder(expr).optimize().build(); for (matches, check) in [ @@ -264,8 +264,8 @@ mod tests { let expr = Expression::all(|expr| { expr.with(selector!(provider = "file")?)? .with(Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })) })?; let condition = Condition::builder(expr).build(); @@ -289,8 +289,8 @@ mod tests { let expr = Expression::all(|expr| { expr.with(selector!(provider = "file")?)? .with(Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })) })?; let condition = Condition::builder(expr).optimize().build(); @@ -316,8 +316,8 @@ mod tests { .with(Expression::any(|expr| { expr.with(selector!(context = "docs")?)? // fmt .with(Expression::not(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) }), ) })) @@ -347,8 +347,8 @@ mod tests { .with(Expression::any(|expr| { expr.with(selector!(context = "docs")?)? // fmt .with(Expression::not(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) }), ) })) diff --git a/crates/zrx-id/src/id/filter/condition/builder.rs b/crates/zrx-id/src/id/filter/condition/builder.rs index 50fd388..be159c0 100644 --- a/crates/zrx-id/src/id/filter/condition/builder.rs +++ b/crates/zrx-id/src/id/filter/condition/builder.rs @@ -247,15 +247,15 @@ mod tests { #[test] fn handles_expression() -> Result { let expr = Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?; let builder = Condition::builder(expr); assert_eq!( builder.terms, [ - Term::from(selector!(location = "**/*.png")?), Term::from(selector!(location = "**/*.jpg")?), + Term::from(selector!(location = "**/*.png")?), ] ); match builder.group { @@ -312,8 +312,8 @@ mod tests { #[test] fn handles_any() -> Result { let expr = Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?; let builder = Condition::builder(expr).optimize(); assert_eq!( @@ -327,8 +327,8 @@ mod tests { fn handles_any_any() -> Result { let expr = Expression::any(|expr| { expr.with(Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?) })?; let builder = Condition::builder(expr).optimize(); @@ -342,9 +342,9 @@ mod tests { #[test] fn handles_any_any_mixed() -> Result { let expr = Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? // fmt + expr.with(selector!(location = "**/*.jpg")?)? // fmt .with(Expression::any(|expr| { - expr.with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.png")?) })?) })?; let builder = Condition::builder(expr).optimize(); @@ -359,8 +359,8 @@ mod tests { fn handles_all_all() -> Result { let expr = Expression::all(|expr| { expr.with(Expression::all(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?) })?; let builder = Condition::builder(expr).optimize(); @@ -377,9 +377,9 @@ mod tests { #[test] fn handles_all_all_mixed() -> Result { let expr = Expression::all(|expr| { - expr.with(selector!(location = "**/*.png")?)? // fmt + expr.with(selector!(location = "**/*.jpg")?)? // fmt .with(Expression::all(|expr| { - expr.with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.png")?) })?) })?; let builder = Condition::builder(expr).optimize(); @@ -397,8 +397,8 @@ mod tests { fn handles_not_not() -> Result { let expr = Expression::not(|expr| { expr.with(Expression::not(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })?) })?; let builder = Condition::builder(expr).optimize(); @@ -418,9 +418,9 @@ mod tests { #[test] fn handles_not_not_mixed() -> Result { let expr = Expression::not(|expr| { - expr.with(selector!(location = "**/*.png")?)? // fmt + expr.with(selector!(location = "**/*.jpg")?)? // fmt .with(Expression::not(|expr| { - expr.with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.png")?) })?) })?; let builder = Condition::builder(expr).optimize(); @@ -444,8 +444,8 @@ mod tests { fn handles_all_any() -> Result { let expr = Expression::all(|expr| { expr.with(Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })) })?; let builder = Condition::builder(expr).optimize(); @@ -467,8 +467,8 @@ mod tests { let expr = Expression::all(|expr| { expr.with(selector!(provider = "file")?)? .with(Expression::any(|expr| { - expr.with(selector!(location = "**/*.png")?)? - .with(selector!(location = "**/*.jpg")?) + expr.with(selector!(location = "**/*.jpg")?)? + .with(selector!(location = "**/*.png")?) })) })?; let builder = Condition::builder(expr).optimize(); diff --git a/crates/zrx-id/src/id/filter/error.rs b/crates/zrx-id/src/id/filter/error.rs index 5787b79..5d9f66d 100644 --- a/crates/zrx-id/src/id/filter/error.rs +++ b/crates/zrx-id/src/id/filter/error.rs @@ -25,7 +25,7 @@ //! Filter error. -use std::result; +use std::{num, result}; use thiserror::Error; use crate::id::matcher; @@ -39,6 +39,9 @@ use super::expression; /// Filter error. #[derive(Debug, Error)] pub enum Error { + /// Numeric conversion error. + #[error(transparent)] + Numeric(#[from] num::TryFromIntError), /// Expression error. #[error(transparent)] Expression(#[from] expression::Error), diff --git a/crates/zrx-id/src/id/filter/expression.rs b/crates/zrx-id/src/id/filter/expression.rs index b05626c..8760b14 100644 --- a/crates/zrx-id/src/id/filter/expression.rs +++ b/crates/zrx-id/src/id/filter/expression.rs @@ -71,7 +71,7 @@ pub use operand::{Operand, Operator, Term}; /// # Ok(()) /// # } /// ``` -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Expression { /// Expression operator. operator: Operator, @@ -96,6 +96,18 @@ impl Expression { pub fn operands(&self) -> &[Operand] { &self.operands } + + /// Returns the number of operands. + #[inline] + pub fn len(&self) -> usize { + self.operands.len() + } + + /// Returns whether there are any operands. + #[inline] + pub fn is_empty(&self) -> bool { + self.operands.is_empty() + } } // ---------------------------------------------------------------------------- @@ -134,8 +146,8 @@ impl IntoIterator for Expression { /// /// // Create expression /// let expr = Expression::any(|expr| { - /// expr.with(selector!(location = "**/*.png")?)? - /// .with(selector!(location = "**/*.jpg")?) + /// expr.with(selector!(location = "**/*.jpg")?)? + /// .with(selector!(location = "**/*.png")?) /// })?; /// /// // Create iterator over expression @@ -150,3 +162,34 @@ impl IntoIterator for Expression { self.operands.into_iter() } } + +// ---------------------------------------------------------------------------- + +impl Default for Expression { + /// Creates an expression that matches everything. + /// + /// While it may seem counterintuitive to have the default expression match + /// everything, it is designed this way to align with the concept of vacuous + /// truth in logic. An expression with no operands is considered to be true, + /// as there are no conditions to violate it. + /// + /// In our implementation, we use an empty expression with a logical `NOT` + /// operator to represent this concept, to be distinguishable as a marker. + /// + /// # Examples + /// + /// ``` + /// use zrx_id::Expression; + /// + /// // Create empty expression + /// let expr = Expression::default(); + /// assert!(expr.is_empty()); + /// ``` + #[inline] + fn default() -> Self { + Expression { + operator: Operator::Not, + operands: Vec::new(), + } + } +} diff --git a/crates/zrx-id/src/id/filter/expression/builder.rs b/crates/zrx-id/src/id/filter/expression/builder.rs index d847f22..a7cd2a1 100644 --- a/crates/zrx-id/src/id/filter/expression/builder.rs +++ b/crates/zrx-id/src/id/filter/expression/builder.rs @@ -64,8 +64,8 @@ impl Expression { /// /// // Create expression /// let expr = Expression::any(|expr| { - /// expr.with(selector!(location = "**/*.png")?)? - /// .with(selector!(location = "**/*.jpg")?) + /// expr.with(selector!(location = "**/*.jpg")?)? + /// .with(selector!(location = "**/*.png")?) /// })?; /// # Ok(()) /// # } @@ -176,8 +176,8 @@ impl Builder { /// /// // Create expression /// let expr = Expression::any(|expr| { - /// expr.with(selector!(location = "**/*.png")?)? - /// .with(selector!(location = "**/*.jpg")?) + /// expr.with(selector!(location = "**/*.jpg")?)? + /// .with(selector!(location = "**/*.png")?) /// })?; /// # Ok(()) /// # } diff --git a/crates/zrx-id/src/id/filter/expression/operand.rs b/crates/zrx-id/src/id/filter/expression/operand.rs index e624a23..bf3722a 100644 --- a/crates/zrx-id/src/id/filter/expression/operand.rs +++ b/crates/zrx-id/src/id/filter/expression/operand.rs @@ -42,7 +42,7 @@ pub use term::Term; // ---------------------------------------------------------------------------- /// Operand. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub enum Operand { /// Expression. Expression(Expression), diff --git a/crates/zrx-id/src/id/filter/terms.rs b/crates/zrx-id/src/id/filter/terms.rs index aa7b9c8..c053d44 100644 --- a/crates/zrx-id/src/id/filter/terms.rs +++ b/crates/zrx-id/src/id/filter/terms.rs @@ -49,6 +49,31 @@ pub struct Terms<'a> { impl Filter { /// Creates an iterator over the terms. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::{selector, Expression, Filter}; + /// + /// // Create filter builder and insert expression + /// let mut builder = Filter::builder(); + /// builder.insert(Expression::any(|expr| { + /// expr.with(selector!(location = "**/*.jpg")?)? + /// .with(selector!(location = "**/*.png")?) + /// })?); + /// + /// // Create filter from builder + /// let filter = builder.build()?; + /// + /// // Create iterator over terms + /// for term in filter.terms() { + /// println!("{term:?}"); + /// } + /// # Ok(()) + /// # } + /// ``` #[inline] #[must_use] pub fn terms(&self) -> Terms<'_> { diff --git a/crates/zrx-id/src/id/matcher/builder.rs b/crates/zrx-id/src/id/matcher/builder.rs index 1821223..934c59e 100644 --- a/crates/zrx-id/src/id/matcher/builder.rs +++ b/crates/zrx-id/src/id/matcher/builder.rs @@ -168,7 +168,6 @@ impl Builder { /// Returns [`Error::Glob`][] if a component's [`GlobSet`][] can't be built. /// /// [`Error::Glob`]: crate::id::matcher::error::Error::Glob - /// /// [`GlobSet`]: globset::GlobSet /// /// # Examples diff --git a/crates/zrx-id/src/id/matcher/component/builder.rs b/crates/zrx-id/src/id/matcher/component/builder.rs index 9f8bcb9..4a09128 100644 --- a/crates/zrx-id/src/id/matcher/component/builder.rs +++ b/crates/zrx-id/src/id/matcher/component/builder.rs @@ -84,7 +84,6 @@ impl Builder { /// Returns [`Error::Glob`][] if the [`GlobSet`][] can't be built. /// /// [`Error::Glob`]: crate::id::matcher::error::Error::Glob - /// /// [`GlobSet`]: globset::GlobSet pub fn build(self) -> Result { Ok(Component { diff --git a/crates/zrx-id/src/id/matcher/selector.rs b/crates/zrx-id/src/id/matcher/selector.rs index 840fc08..53a93bc 100644 --- a/crates/zrx-id/src/id/matcher/selector.rs +++ b/crates/zrx-id/src/id/matcher/selector.rs @@ -52,7 +52,7 @@ pub use convert::TryToSelector; /// /// Selectors are used to match identifiers. Like identifiers, they consist of /// six components, which can be set to specific values or left empty to act as -/// wildcards. Each components can be set to a glob as supported by [`globset`], +/// wildcards. Each component can be set to a glob as supported by [`globset`], /// which allows for powerful matching capabilities. /// /// Selectors are no means to an end, but rather a building block to associate diff --git a/crates/zrx-id/src/id/matcher/selector/builder.rs b/crates/zrx-id/src/id/matcher/selector/builder.rs index 226d773..c6a0dd9 100644 --- a/crates/zrx-id/src/id/matcher/selector/builder.rs +++ b/crates/zrx-id/src/id/matcher/selector/builder.rs @@ -83,11 +83,11 @@ impl Selector { /// let selector: Selector = "zrs:::::**/*.md:".parse()?; /// /// // Create selector builder - /// let builder = selector.to_builder().location("**/index.md"); + /// let builder = selector.to_builder().location("index.md"); /// /// // Create selector from builder /// let selector = builder.build()?; - /// assert_eq!(selector.as_str(), "zrs:::::**/index.md:"); + /// assert_eq!(selector.as_str(), "zrs:::::index.md:"); /// # Ok(()) /// # } /// ``` diff --git a/crates/zrx-id/src/id/specificity.rs b/crates/zrx-id/src/id/specificity.rs index 1fbf85c..0511aee 100644 --- a/crates/zrx-id/src/id/specificity.rs +++ b/crates/zrx-id/src/id/specificity.rs @@ -27,17 +27,104 @@ use std::cmp::{self, Ordering}; -pub mod convert; +mod convert; pub mod segment; +mod specified; mod tokens; -use convert::ToSpecificity; +pub use convert::ToSpecificity; +pub use specified::Specified; // ---------------------------------------------------------------------------- // Structs // ---------------------------------------------------------------------------- /// Specificity. +/// +/// Specificity is an ordering and tie-breaking concept borrowed from CSS, where +/// more specific selectors take precedence over less specific ones. Specificity +/// is computable for the likes of [`Expression`][], [`Term`][], [`Operand`][], +/// [`Id`][], [`Selector`][], and [`Glob`][]. +/// +/// # Representation +/// +/// Specificity is represented as a 4-tuple `(a, b, c, l)`, which is compared +/// in lexicographic order, meaning that components are compared in sequence: +/// +/// - `a` – number of segments with literals only, e.g. `src`, `main.rs`. +/// - `b` – number of segments with single-wildcards, e.g. `*`, `?`, `[abc]`. +/// - `c` – number of segments with double-wildcards, compared in reverse. +/// - `l` – number of literals across all segments. +/// +/// # Atoms +/// +/// In a [`Segment`][], atoms are combined with [`Specificity::min_sum_len`] - +/// the structural component `a`, `b`, or `c` is assigned by taking the minimum +/// across all atoms in the segment, whereas the length component `l` receives +/// the sum across all atoms in the segment. +/// +/// # Ids and selectors +/// +/// The specificity of an [`Id`][] or [`Selector`][] is computed by summing the +/// specificities of its components, where the specificity of each component is +/// computed individually and then combined with [`Specificity::sum`][]. Empty +/// components receive the [`Specificity::default`], which is `(0, 0, 0, 0)`. +/// +/// ``` sh +/// zrs:{git,file}:::{docs}:index.md: # (3, 0, 0, 15) +/// zrs::::docs:{index,about}.md: # (2, 0, 0, 12) +/// zrs:::::index.{md,rst}: # (1, 0, 0, 8) +/// zrs:::::{*}: # (0, 1, 0, 0) +/// ``` +/// +/// # Expressions +/// +/// An [`Expression`][] is a combination of multiple [`Id`][] and [`Selector`][] +/// terms, with its specificity computed according to its [`Operator`][]: +/// +/// - [`Expression::any`][]: takes the minimum. The expression is as specific +/// as its least specific operand, since any operand can match. +/// +/// - [`Expression::all`][]: sums specificities. The expression is as specific +/// as the combination of all its operands, since all operands must match. +/// +/// - [`Expression::not`][]: contributes nothing, i.e., `(0, 0, 0, 0)`, since +/// a negation is a guard that filters matches but does not select them. +/// +/// Alternate groups, e.g. `{jpg,png}`, are equivalent to [`Expression::any`][] +/// at the [`Atom`][] level and follow the same rules. +/// +/// [`Atom`]: crate::id::specificity::segment::Atom +/// [`Expression`]: crate::id::filter::Expression +/// [`Expression::all`]: crate::id::filter::Expression::all +/// [`Expression::any`]: crate::id::filter::Expression::any +/// [`Expression::not`]: crate::id::filter::Expression::not +/// [`Glob`]: globset::Glob +/// [`Id`]: crate::id::Id +/// [`Operand`]: crate::id::filter::expression::Operand +/// [`Operator`]: crate::id::filter::expression::Operator +/// [`Segment`]: crate::id::specificity::segment::Segment +/// [`Selector`]: crate::id::matcher::selector::Selector +/// [`Term`]: crate::id::filter::Term +/// +/// # Examples +/// +/// ``` +/// # use std::error::Error; +/// # fn main() -> Result<(), Box> { +/// use zrx_id::filter::Expression; +/// use zrx_id::selector; +/// use zrx_id::specificity::ToSpecificity; +/// +/// // Create expression and compute specificity +/// let expr = Expression::any(|expr| { +/// expr.with(selector!(location = "**/*.jpg")?)? +/// .with(selector!(location = "**/*.png")?) +/// })?; +/// assert_eq!(expr.to_specificity(), (0, 1, 1, 4).into()); +/// # Ok(()) +/// # } +/// ``` #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Specificity(u16, u16, u16, u16); @@ -51,7 +138,12 @@ impl Specificity { fn sum(self, other: Self) -> Self { let Specificity(a1, b1, c1, l1) = self; let Specificity(a2, b2, c2, l2) = other; - Self(a1 + a2, b1 + b2, c1 + c2, l1 + l2) + Self( + a1.saturating_add(a2), + b1.saturating_add(b2), + c1.saturating_add(c2), + l1.saturating_add(l2), + ) } /// Computes the minimum of both specificities. @@ -60,7 +152,7 @@ impl Specificity { cmp::min(self, other) } - /// Computes the minimum of both specificities while summing their lengths. + /// Computes the minimum of both specificities, summing their lengths. #[inline] fn min_sum_len(self, other: Self) -> Self { let mut spec = cmp::min(self, other); @@ -84,10 +176,34 @@ where } } +impl From<(u16, u16, u16, u16)> for Specificity { + /// Creates a specificity from a tuple. + #[inline] + fn from((a, b, c, l): (u16, u16, u16, u16)) -> Self { + Self(a, b, c, l) + } +} + // ---------------------------------------------------------------------------- impl PartialOrd for Specificity { /// Orders two specificities. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::selector; + /// use zrx_id::specificity::ToSpecificity; + /// + /// // Create and compare selectors by specificity + /// let a = selector!(location = "**/*.md")?; + /// let b = selector!(location = "*.md")?; + /// assert!(a.to_specificity() < b.to_specificity()); + /// # Ok(()) + /// # } + /// ``` #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -96,13 +212,41 @@ impl PartialOrd for Specificity { impl Ord for Specificity { /// Orders two specificities. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::selector; + /// use zrx_id::specificity::ToSpecificity; + /// + /// // Create and compare selectors by specificity + /// let a = selector!(location = "**/*.md")?; + /// let b = selector!(location = "*.md")?; + /// assert!(a.to_specificity() < b.to_specificity()); + /// # Ok(()) + /// # } + /// ``` #[inline] fn cmp(&self, other: &Self) -> Ordering { let Specificity(a1, b1, c1, l1) = self; let Specificity(a2, b2, c2, l2) = other; + + // An all-zero specificity is the least specific and must always be the + // first in order, so check if this applies to any of the specificities + if a1 | b1 | c1 | l1 == 0 { + return Ordering::Less; + } + if a2 | b2 | c2 | l2 == 0 { + return Ordering::Greater; + } + + // Otherwise, compare each component, where `c` is reversed since fewer + // double-wildcards is more specific than more double-wildcards a1.cmp(a2) - .then(b1.cmp(b2)) - .then(c2.cmp(c1)) // reversed: fewer ** = more specific - .then(l1.cmp(l2)) + .then_with(|| b1.cmp(b2)) + .then_with(|| c2.cmp(c1)) + .then_with(|| l1.cmp(l2)) } } diff --git a/crates/zrx-id/src/id/specificity/convert.rs b/crates/zrx-id/src/id/specificity/convert.rs index d7da07b..83901a0 100644 --- a/crates/zrx-id/src/id/specificity/convert.rs +++ b/crates/zrx-id/src/id/specificity/convert.rs @@ -23,9 +23,7 @@ // ---------------------------------------------------------------------------- -//! Specificity conversion. - -use std::cmp; +//! Specificity computation. use crate::id::filter::expression::{Operand, Operator}; use crate::id::filter::{Expression, Term}; @@ -33,15 +31,14 @@ use crate::id::matcher::selector::Selector; use crate::id::Id; use super::segment::atom::{Character, Wildcard}; -use super::segment::convert::ToSegments; -use super::segment::{Atom, Segment, Segments}; +use super::segment::{Atom, Segment, Segments, ToSegments}; use super::Specificity; // ---------------------------------------------------------------------------- // Traits // ---------------------------------------------------------------------------- -/// Computes the [`Specificity`]. +/// Computation of [`Specificity`]. pub trait ToSpecificity { /// Computes the specificity of the value. fn to_specificity(&self) -> Specificity; @@ -53,6 +50,25 @@ pub trait ToSpecificity { impl ToSpecificity for Expression { /// Computes the specificity of the expression. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::filter::Expression; + /// use zrx_id::selector; + /// use zrx_id::specificity::ToSpecificity; + /// + /// // Create expression and compute specificity + /// let expr = Expression::any(|expr| { + /// expr.with(selector!(location = "**/*.jpg")?)? + /// .with(selector!(location = "**/*.png")?) + /// })?; + /// assert_eq!(expr.to_specificity(), (0, 1, 1, 4).into()); + /// # Ok(()) + /// # } + /// ``` #[inline] fn to_specificity(&self) -> Specificity { let iter = self.operands().iter().map(ToSpecificity::to_specificity); @@ -66,6 +82,22 @@ impl ToSpecificity for Expression { impl ToSpecificity for Term { /// Computes the specificity of the term. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::filter::expression::Term; + /// use zrx_id::selector; + /// use zrx_id::specificity::ToSpecificity; + /// + /// // Create term and compute specificity + /// let term = Term::from(selector!(location = "**/*.md")?); + /// assert_eq!(term.to_specificity(), (0, 1, 1, 3).into()); + /// # Ok(()) + /// # } + /// ``` #[inline] fn to_specificity(&self) -> Specificity { match self { @@ -77,6 +109,22 @@ impl ToSpecificity for Term { impl ToSpecificity for Operand { /// Computes the specificity of the operand. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::filter::expression::Operand; + /// use zrx_id::selector; + /// use zrx_id::specificity::ToSpecificity; + /// + /// // Create operand and compute specificity + /// let operand = Operand::from(selector!(location = "**/*.md")?); + /// assert_eq!(operand.to_specificity(), (0, 1, 1, 3).into()); + /// # Ok(()) + /// # } + /// ``` #[inline] fn to_specificity(&self) -> Specificity { match self { @@ -90,6 +138,21 @@ impl ToSpecificity for Operand { impl ToSpecificity for Id { /// Computes the specificity of the identifier. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::id; + /// use zrx_id::specificity::ToSpecificity; + /// + /// // Create identifier and compute specificity + /// let id = id!(provider = "file", context = ".", location = "index.md")?; + /// assert_eq!(id.to_specificity(), (3, 0, 0, 13).into()); + /// # Ok(()) + /// # } + /// ``` #[inline] fn to_specificity(&self) -> Specificity { let iter = 1..7; @@ -101,6 +164,21 @@ impl ToSpecificity for Id { impl ToSpecificity for Selector { /// Computes the specificity of the selector. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::selector; + /// use zrx_id::specificity::ToSpecificity; + /// + /// // Create selector and compute specificity + /// let selector = selector!(location = "**/*.md")?; + /// assert_eq!(selector.to_specificity(), (0, 1, 1, 3).into()); + /// # Ok(()) + /// # } + /// ``` #[inline] fn to_specificity(&self) -> Specificity { let iter = 1..7; @@ -113,7 +191,7 @@ impl ToSpecificity for Selector { // ---------------------------------------------------------------------------- impl ToSpecificity for Segments<'_> { - /// Computes the specificity of the segments set. + /// Computes the specificity of the segment set. #[inline] fn to_specificity(&self) -> Specificity { self.iter() @@ -150,7 +228,7 @@ impl ToSpecificity for Atom<'_> { Atom::Group(data) => data .iter() .map(ToSpecificity::to_specificity) - .reduce(cmp::min) + .reduce(Specificity::min) .unwrap_or_default(), } } @@ -169,7 +247,7 @@ impl ToSpecificity for Wildcard { } impl ToSpecificity for Character<'_> { - /// Computes the specificity of the character. + /// Computes the specificity of the character class. #[inline] fn to_specificity(&self) -> Specificity { Specificity(0, 1, 0, 1) diff --git a/crates/zrx-id/src/id/specificity/segment.rs b/crates/zrx-id/src/id/specificity/segment.rs index da852b6..0cba4ce 100644 --- a/crates/zrx-id/src/id/specificity/segment.rs +++ b/crates/zrx-id/src/id/specificity/segment.rs @@ -29,10 +29,11 @@ use std::fmt::{self, Display}; use std::slice::Iter; pub mod atom; -pub mod convert; +mod convert; mod segments; pub use atom::Atom; +pub use convert::ToSegments; pub use segments::Segments; // ---------------------------------------------------------------------------- diff --git a/crates/zrx-id/src/id/specificity/segment/atom/character.rs b/crates/zrx-id/src/id/specificity/segment/atom/character.rs index 43d4004..538b148 100644 --- a/crates/zrx-id/src/id/specificity/segment/atom/character.rs +++ b/crates/zrx-id/src/id/specificity/segment/atom/character.rs @@ -32,29 +32,40 @@ use std::fmt::{self, Display, Write}; // ---------------------------------------------------------------------------- /// Character class. +/// +/// For our case, we do not need to know the exact structure of the character +/// class, as we'll score it as a single character anyway, same as `*`. We also +/// don't care whether it's negated or not, as that doesn't affect scoring as +/// well. Therefore, we can just store the string slices. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Character<'a> { - /// Negation marker. - pub negate: bool, - /// Characters. - pub values: Vec<&'a str>, + /// String slices. + values: Vec<&'a str>, } // ---------------------------------------------------------------------------- // Trait implementations // ---------------------------------------------------------------------------- +impl<'a> FromIterator<&'a str> for Character<'a> { + /// Creates a character class from an iterator. + #[inline] + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + Character { + values: iter.into_iter().collect(), + } + } +} + +// ---------------------------------------------------------------------------- + impl Display for Character<'_> { /// Formats the character class for display. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_char('[')?; - - // Write negation marker - if self.negate { - f.write_char('!')?; - } - - // Write characters for value in &self.values { f.write_str(value)?; } diff --git a/crates/zrx-id/src/id/specificity/segment/convert.rs b/crates/zrx-id/src/id/specificity/segment/convert.rs index b196cbc..27738ba 100644 --- a/crates/zrx-id/src/id/specificity/segment/convert.rs +++ b/crates/zrx-id/src/id/specificity/segment/convert.rs @@ -39,7 +39,7 @@ use super::Segment; /// Conversion to [`Segments`]. pub trait ToSegments { - /// Converts to a segments set. + /// Converts to a segment set. fn to_segments(&self) -> Segments<'_>; } @@ -51,7 +51,17 @@ impl ToSegments for T where T: AsTokens, { - /// Converts tokens to a segments set. + /// Converts tokens to a segment set. + /// + /// # Examples + /// + /// ``` + /// use zrx_id::specificity::segment::ToSegments; + /// + /// // Create segment set from string + /// let segments = "**/*.md".to_segments(); + /// assert_eq!(segments.len(), 2); + /// ``` #[inline] fn to_segments(&self) -> Segments<'_> { parse(&mut self.as_tokens().peekable(), false) @@ -115,16 +125,6 @@ fn parse_segment<'a>(iter: &mut Iter<'a>, group: bool) -> Segment<'a> { fn parse_character<'a>(iter: &mut Iter<'a>) -> Character<'a> { let mut values = Vec::new(); - // Consume negation marker if present - let negate = match iter.next() { - Some(Token::Exclamation) => true, - None => false, - Some(token) => { - values.push(token.as_str()); - false - } - }; - // Consume tokens until character class end for token in iter.by_ref() { values.push(match token { @@ -134,7 +134,7 @@ fn parse_character<'a>(iter: &mut Iter<'a>) -> Character<'a> { } // Return character class - Character { negate, values } + Character::from_iter(values) } /// Parses a sequence of tokens into a group of segments. diff --git a/crates/zrx-id/src/id/specificity/segment/segments.rs b/crates/zrx-id/src/id/specificity/segment/segments.rs index db0fbfb..9e60ea2 100644 --- a/crates/zrx-id/src/id/specificity/segment/segments.rs +++ b/crates/zrx-id/src/id/specificity/segment/segments.rs @@ -52,12 +52,41 @@ pub struct Segments<'a> { impl Segments<'_> { /// Creates an iterator over the segment set. + /// + /// # Examples + /// + /// ``` + /// use zrx_id::specificity::segment::ToSegments; + /// + /// // Create segment set from string + /// let segments = "**/*.md".to_segments(); + /// + /// // Create iterator over segment set + /// for segment in segments.iter() { + /// println!("{segment:?}"); + /// } + /// ``` #[inline] pub fn iter(&self) -> Iter<'_, Segment<'_>> { self.inner.iter() } } +#[allow(clippy::must_use_candidate)] +impl Segments<'_> { + /// Returns the number of segments. + #[inline] + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Returns whether there are any segments. + #[inline] + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } +} + // ---------------------------------------------------------------------------- // Trait implementations // ---------------------------------------------------------------------------- @@ -80,6 +109,20 @@ impl<'a> IntoIterator for &'a Segments<'a> { type IntoIter = Iter<'a, Segment<'a>>; /// Creates an iterator over the segment set. + /// + /// # Examples + /// + /// ``` + /// use zrx_id::specificity::segment::ToSegments; + /// + /// // Create segment set from string + /// let segments = "**/*.md".to_segments(); + /// + /// // Create iterator over segment set + /// for segment in &segments { + /// println!("{segment:?}"); + /// } + /// ``` #[inline] fn into_iter(self) -> Self::IntoIter { self.iter() diff --git a/crates/zrx-id/src/id/specificity/specified.rs b/crates/zrx-id/src/id/specificity/specified.rs new file mode 100644 index 0000000..413ef0c --- /dev/null +++ b/crates/zrx-id/src/id/specificity/specified.rs @@ -0,0 +1,225 @@ +// Copyright (c) 2025-2026 Zensical and contributors + +// SPDX-License-Identifier: MIT +// All contributions are certified under the DCO + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// ---------------------------------------------------------------------------- + +//! Specified value. + +use std::cmp::Ordering; +use std::ops::Deref; + +use super::convert::ToSpecificity; +use super::Specificity; + +// ---------------------------------------------------------------------------- +// Structs +// ---------------------------------------------------------------------------- + +/// Specified value. +/// +/// This data type is a thin wrapper around a value of type `T`, augmenting the +/// value with its computed [`Specificity`], so it can be efficiently ordered. +/// +/// Note that specifities are ordered from lowest to highest, with the least +/// specific value being the first in order. It would be natural to reverse +/// this order, but this would lead to more complex implementations for when +/// a specified value is wrapped in an [`Option`]. +#[derive(Clone, Debug, Default)] +pub struct Specified { + /// Inner value. + inner: T, + /// Specificity. + specificity: Specificity, +} + +// ---------------------------------------------------------------------------- +// Implementations +// ---------------------------------------------------------------------------- + +impl Specified { + /// Returns the inner value, consuming the specified value. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::selector; + /// use zrx_id::specificity::Specified; + /// + /// // Create selector + /// let selector = selector!(location = "**/*.md")?; + /// + /// // Create specified value from selector + /// let value = Specified::from(selector.clone()); + /// assert_eq!(value.into_inner(), selector); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn into_inner(self) -> T { + self.inner + } +} + +#[allow(clippy::must_use_candidate)] +impl Specified { + /// Returns the specificity. + #[inline] + pub fn specificity(&self) -> Specificity { + self.specificity + } +} + +// ---------------------------------------------------------------------------- +// Trait implementations +// ---------------------------------------------------------------------------- + +impl From for Specified +where + T: ToSpecificity, +{ + /// Creates a specified value from a value. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::selector; + /// use zrx_id::specificity::Specified; + /// + /// // Create selector + /// let selector = selector!(location = "**/*.md")?; + /// + /// // Create specified value from selector + /// let value = Specified::from(selector); + /// assert_eq!(value.specificity(), (0, 1, 1, 3).into()); + /// # Ok(()) + /// # } + /// ``` + #[inline] + fn from(inner: T) -> Self { + let specificity = inner.to_specificity(); + Specified { inner, specificity } + } +} + +// ---------------------------------------------------------------------------- + +impl Deref for Specified { + type Target = T; + + /// Dereferences to the inner value. + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +// ---------------------------------------------------------------------------- + +impl PartialEq for Specified +where + T: PartialEq, +{ + /// Compares two specified values for equality. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::selector; + /// use zrx_id::specificity::Specified; + /// + /// // Create and compare selectors + /// let a = Specified::from(selector!(location = "*.md")?); + /// let b = Specified::from(selector!(location = "*.md")?); + /// assert_eq!(a, b); + /// # Ok(()) + /// # } + /// ``` + #[inline] + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl Eq for Specified where T: Eq {} + +// ---------------------------------------------------------------------------- + +impl PartialOrd for Specified +where + T: Eq, +{ + /// Orders two values by specificity. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::selector; + /// use zrx_id::specificity::Specified; + /// + /// // Create and compare selectors by specificity + /// let a = Specified::from(selector!(location = "**/*.md")?); + /// let b = Specified::from(selector!(location = "*.md")?); + /// assert!(a < b); + /// # Ok(()) + /// # } + /// ``` + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Specified +where + T: Eq, +{ + /// Orders two values by specificity. + /// + /// # Examples + /// + /// ``` + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// use zrx_id::selector; + /// use zrx_id::specificity::Specified; + /// + /// // Create and compare selectors by specificity + /// let a = Specified::from(selector!(location = "**/*.md")?); + /// let b = Specified::from(selector!(location = "*.md")?); + /// assert!(a < b); + /// # Ok(()) + /// # } + /// ``` + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + self.specificity.cmp(&other.specificity) + } +} diff --git a/crates/zrx-id/src/id/specificity/tokens.rs b/crates/zrx-id/src/id/specificity/tokens.rs index 0c744a3..4e3e65e 100644 --- a/crates/zrx-id/src/id/specificity/tokens.rs +++ b/crates/zrx-id/src/id/specificity/tokens.rs @@ -50,8 +50,6 @@ pub enum Token<'a> { CharacterStart, /// Character class end - `]` CharacterEnd, - /// Exclamation mark - `!` - Exclamation, /// Alternate group start - `{` GroupStart, /// Alternate group end - `}` @@ -98,7 +96,6 @@ impl<'a> Token<'a> { Token::StarStar => "**", Token::CharacterStart => "[", Token::CharacterEnd => "]", - Token::Exclamation => "!", Token::GroupStart => "{", Token::GroupEnd => "}", Token::Comma => ",", @@ -110,7 +107,7 @@ impl<'a> Token<'a> { // ---------------------------------------------------------------------------- impl<'a> From<&'a str> for Tokens<'a> { - /// Creates an iterator over the tokens of a pattern. + /// Creates an iterator over tokens from a string slice. #[inline] fn from(value: &'a str) -> Self { Self { value, index: 0 } @@ -146,13 +143,12 @@ impl<'a> Iterator for Tokens<'a> { b'?' => Some(Token::Any), b'[' => Some(Token::CharacterStart), b']' => Some(Token::CharacterEnd), - b'!' => Some(Token::Exclamation), b'{' => Some(Token::GroupStart), b'}' => Some(Token::GroupEnd), b',' => Some(Token::Comma), b'/' => Some(Token::Separator), - // Consume a `*` or `**` + // Consume `*` or `**` b'*' => { if self.index < value.len() && value[self.index] == b'*' { self.index += 1; @@ -185,6 +181,6 @@ impl<'a> Iterator for Tokens<'a> { fn is_special(char: u8) -> bool { matches!( char, - b'.' | b'?' | b'*' | b'[' | b']' | b'!' | b'{' | b'}' | b',' | b'/' + b'.' | b'?' | b'*' | b'[' | b']' | b'{' | b'}' | b',' | b'/' ) } diff --git a/crates/zrx-id/src/lib.rs b/crates/zrx-id/src/lib.rs index 53b394c..8eef91c 100644 --- a/crates/zrx-id/src/lib.rs +++ b/crates/zrx-id/src/lib.rs @@ -29,8 +29,7 @@ mod id; -pub use id::filter::expression::Expression; -pub use id::filter::{self, Filter}; +pub use id::filter::{self, Expression, Filter}; pub use id::format; pub use id::matcher::selector::{Selector, TryToSelector}; pub use id::matcher::{self, Matcher, Matches}; diff --git a/crates/zrx-store/src/store/collection.rs b/crates/zrx-store/src/store/collection.rs index 56af5f8..af52d4e 100644 --- a/crates/zrx-store/src/store/collection.rs +++ b/crates/zrx-store/src/store/collection.rs @@ -100,7 +100,7 @@ pub trait Collection: Any + Debug { // ---------------------------------------------------------------------------- impl dyn Collection { - /// Attempts to downcast the collection to a reference of `T`. + /// Attempts to downcast to a reference of `T`. #[inline] #[must_use] pub fn downcast_ref(&self) -> Option<&T> @@ -110,7 +110,7 @@ impl dyn Collection { (self as &dyn Any).downcast_ref() } - /// Attempts to downcast the collection to a mutable reference of `T`. + /// Attempts to downcast to a mutable reference of `T`. #[inline] #[must_use] pub fn downcast_mut(&mut self) -> Option<&mut T> diff --git a/crates/zrx-store/src/store/comparator/comparable.rs b/crates/zrx-store/src/store/comparator/comparable.rs index 04f3832..6024f4b 100644 --- a/crates/zrx-store/src/store/comparator/comparable.rs +++ b/crates/zrx-store/src/store/comparator/comparable.rs @@ -51,8 +51,8 @@ use super::{Ascending, Comparator}; /// use zrx_store::comparator::Comparable; /// /// // Create and compare values -/// let a: Comparable<_> = 42.into(); -/// let b: Comparable<_> = 84.into(); +/// let a = Comparable::from(42); +/// let b = Comparable::from(84); /// assert!(a < b); /// ``` #[derive(Clone)] @@ -109,7 +109,7 @@ impl From for Comparable { /// use zrx_store::comparator::Comparable; /// /// // Create comparable value - /// let value: Comparable<_> = 42.into(); + /// let value = Comparable::from(42); /// assert_eq!(*value, 42); /// ``` #[inline] @@ -123,7 +123,7 @@ impl From for Comparable { impl Deref for Comparable { type Target = T; - /// Dereferences to the wrapped value. + /// Dereferences to the inner value. #[inline] fn deref(&self) -> &Self::Target { &self.0 @@ -136,7 +136,7 @@ impl PartialEq for Comparable where T: PartialEq, { - /// Compares two values for equality. + /// Compares two comparable values for equality. /// /// # Examples /// @@ -144,8 +144,8 @@ where /// use zrx_store::comparator::Comparable; /// /// // Create and compare values - /// let a: Comparable<_> = 42.into(); - /// let b: Comparable<_> = 42.into(); + /// let a = Comparable::from(42); + /// let b = Comparable::from(42); /// assert_eq!(a, b); /// ``` #[inline] @@ -163,7 +163,7 @@ where T: Eq, C: Comparator, { - /// Orders two values. + /// Orders two comparable values. /// /// # Examples /// @@ -171,8 +171,8 @@ where /// use zrx_store::comparator::Comparable; /// /// // Create and compare values - /// let a: Comparable<_> = 42.into(); - /// let b: Comparable<_> = 84.into(); + /// let a = Comparable::from(42); + /// let b = Comparable::from(84); /// assert!(a < b); /// ``` #[inline] @@ -186,7 +186,7 @@ where T: Eq, C: Comparator, { - /// Orders two values. + /// Orders two comparable values. /// /// # Examples /// @@ -194,8 +194,8 @@ where /// use zrx_store::comparator::Comparable; /// /// // Create and compare values - /// let a: Comparable<_> = 42.into(); - /// let b: Comparable<_> = 84.into(); + /// let a = Comparable::from(42); + /// let b = Comparable::from(84); /// assert!(a < b); /// ``` #[inline]