diff --git a/CHANGELOG.md b/CHANGELOG.md index 13a505e..cac8152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +### Added + +- Add `str::contains_all` function + ## [3.0.3] - 2023-04-13 ### Internal diff --git a/src/lib.rs b/src/lib.rs index ea0214c..5f87523 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,6 +143,7 @@ //! - [`predicate::str::ends_with`]: Specified string must end with the given needle. //! - [`predicate::str::contains`]: Specified string must contain the given needle. //! - [`predicate::str::contains(...).count`]: Required number of times the needle must show up. +//! - [`predicate::str::contains_all`]: Specified string must contain all given needles. //! - [`predicate::str::is_match`]: Specified string must match the given regex. //! - [`predicate::str::is_match(...).count`]: Required number of times the match must show up. //! - [`str_pred.trim`]: Trim whitespace before passing it to `str_pred`. @@ -188,6 +189,7 @@ //! [`predicate::path::missing`]: prelude::predicate::path::missing() //! [`predicate::str::contains(...).count`]: str::ContainsPredicate::count() //! [`predicate::str::contains`]: prelude::predicate::str::contains() +//! [`predicate::str::contains_all`]: prelude::predicate::str::contains_all() //! [`predicate::str::diff`]: prelude::predicate::str::diff() //! [`predicate::str::ends_with`]: prelude::predicate::str::ends_with() //! [`predicate::str::is_empty`]: prelude::predicate::str::is_empty() diff --git a/src/prelude.rs b/src/prelude.rs index 8cfd447..f9413db 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -28,7 +28,7 @@ pub mod predicate { /// This module contains predicates specific to string handling. pub mod str { pub use crate::str::is_empty; - pub use crate::str::{contains, ends_with, starts_with}; + pub use crate::str::{contains, contains_all, ends_with, starts_with}; #[cfg(feature = "diff")] pub use crate::str::diff; diff --git a/src/str/basics.rs b/src/str/basics.rs index 4128a8e..494b34f 100644 --- a/src/str/basics.rs +++ b/src/str/basics.rs @@ -288,3 +288,81 @@ where pattern: pattern.into(), } } + +/// Predicate that checks for all patterns. +/// +/// This is created by `predicates::str:contains_all`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ContainsAllPredicate { + patterns: Vec, +} + +impl Predicate for ContainsAllPredicate { + fn eval(&self, variable: &str) -> bool { + for pattern in &self.patterns { + if !variable.contains(pattern) { + return false; + } + } + + true + } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + let mut missing = None; + + for pattern in &self.patterns { + if !variable.contains(pattern) && !expected { + missing = Some(pattern) + } + } + + match missing { + Some(m) => Some( + reflection::Case::new(Some(self), false) + .add_product(reflection::Product::new("missing", m.to_owned())), + ), + None => None, + } + } +} + +impl reflection::PredicateReflection for ContainsAllPredicate {} + +impl fmt::Display for ContainsAllPredicate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let palette = crate::Palette::new(f.alternate()); + write!( + f, + "{}.{}({})", + palette.var("var"), + palette.description("contains_all"), + palette.expected(format!("{:?}", &self.patterns)), + ) + } +} + +/// Creates a new `Predicate` that ensures a str contains `pattern` +/// +/// # Examples +/// +/// ``` +/// use predicates::prelude::*; +/// +/// let predicate_fn = predicate::str::contains_all(vec!["One", "Two", "Three"]); +/// assert_eq!(true, predicate_fn.eval("One Two Three")); +/// assert_eq!(false, predicate_fn.eval("One Two Four")); +/// assert_eq!(false, predicate_fn.eval("Four Five Six")); +/// ``` +pub fn contains_all(patterns: P) -> ContainsAllPredicate +where + P: IntoIterator, + T: AsRef, +{ + let patterns: Vec<_> = patterns + .into_iter() + .map(|p| p.as_ref().to_string()) + .collect(); + + ContainsAllPredicate { patterns } +}