Skip to content

Commit

Permalink
feat(trait): All Predicates are now Display
Browse files Browse the repository at this point in the history
This is a step towards #7.  We can now display the expression under
test.

Next steps:
- Ellipsis the strings being reported from this change.
- Ability to enumerate failure cases.

I'm mixed about building this enumeration functionality directly into
`Predcate` or if we should instead provide an expanded trait that is
only implemented when `Predicate` is `Display` and `T` is `Debug`.  For
now, I'm going with the simplest implementation, to assume every
predicate will want this functionality.

BREAKING CHANGES:
- `Display` is now required to implement `Predicate`.
- Generic traits (taking `T`) now require `T` to be `Debug`.
  • Loading branch information
epage committed May 15, 2018
1 parent e550a91 commit 0521670
Show file tree
Hide file tree
Showing 15 changed files with 298 additions and 22 deletions.
33 changes: 33 additions & 0 deletions src/boolean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! Definition of boolean logic combinators over `Predicate`s.

use std::marker::PhantomData;
use std::fmt;

use Predicate;

Expand Down Expand Up @@ -54,6 +55,17 @@ where
}
}

impl<M1, M2, Item> fmt::Display for AndPredicate<M1, M2, Item>
where
M1: Predicate<Item>,
M2: Predicate<Item>,
Item: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({} && {})", self.a, self.b)
}
}

/// Predicate that combines two `Predicate`s, returning the OR of the results.
///
/// This is created by the `Predicate::or` function.
Expand Down Expand Up @@ -96,6 +108,17 @@ where
}
}

impl<M1, M2, Item> fmt::Display for OrPredicate<M1, M2, Item>
where
M1: Predicate<Item>,
M2: Predicate<Item>,
Item: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({} || {})", self.a, self.b)
}
}

/// Predicate that returns a `Predicate` taking the logical NOT of the result.
///
/// This is created by the `Predicate::not` function.
Expand Down Expand Up @@ -133,6 +156,16 @@ where
}
}

impl<M, Item> fmt::Display for NotPredicate<M, Item>
where
M: Predicate<Item>,
Item: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "(! {})", self.inner)
}
}

/// `Predicate` extension that adds boolean logic.
pub trait PredicateBooleanExt<Item: ?Sized>
where
Expand Down
2 changes: 1 addition & 1 deletion src/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ where
Item: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "BoxPredicate")
write!(f, "{}", self.0)
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! Definition of a constant (always true or always false) `Predicate`.

use std::marker::PhantomData;
use std::fmt;

use Predicate;

Expand All @@ -27,6 +28,12 @@ impl<Item> Predicate<Item> for BooleanPredicate<Item> {
}
}

impl<Item> fmt::Display for BooleanPredicate<Item> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.retval)
}
}

/// Creates a new `Predicate` that always returns `true`.
///
/// # Examples
Expand Down
4 changes: 3 additions & 1 deletion src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::fmt;

/// Trait for generically evaluating a type against a dynamically created
/// predicate function.
///
/// The exact meaning of `eval` depends on the situation, but will usually
/// mean that the evaluated item is in some sort of pre-defined set. This is
/// different from `Ord` and `Eq` in that an `item` will almost never be the
/// same type as the implementing `Predicate` type.
pub trait Predicate<Item: ?Sized> {
pub trait Predicate<Item: ?Sized>: fmt::Display {
/// Execute this `Predicate` against `variable`, returning the resulting
/// boolean.
fn eval(&self, variable: &Item) -> bool;
Expand Down
12 changes: 12 additions & 0 deletions src/float/close.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::fmt;

use float_cmp::ApproxEq;
use float_cmp::Ulps;

Expand Down Expand Up @@ -84,6 +86,16 @@ impl Predicate<f64> for IsClosePredicate {
}
}

impl fmt::Display for IsClosePredicate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"var ~= {} +/- {} ({})",
self.target, self.epsilon, self.ulps
)
}
}

/// Create a new `Predicate` that ensures two numbers are "close" enough, understanding that
/// rounding errors occur.
///
Expand Down
38 changes: 38 additions & 0 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! Definition of `Predicate` for wrapping a `Fn(&T) -> bool`

use std::marker::PhantomData;
use std::fmt;

use Predicate;

Expand All @@ -20,9 +21,36 @@ where
F: Fn(&T) -> bool,
{
function: F,
name: &'static str,
_phantom: PhantomData<T>,
}

impl<F, T> FnPredicate<F, T>
where
F: Fn(&T) -> bool,
{
/// Provide a descriptive name for this function.
///
/// # Examples
///
/// ```
/// use predicates::prelude::*;
///
/// struct Example {
/// string: String,
/// number: i32,
/// }
///
/// let string_check = predicate::function(|x: &Example| x.string == "hello")
/// .fn_name("is_hello");
/// println!("predicate: {}", string_check);
/// ```
pub fn fn_name(mut self, name: &'static str) -> Self {
self.name = name;
self
}
}

impl<F, T> Predicate<T> for FnPredicate<F, T>
where
F: Fn(&T) -> bool,
Expand All @@ -32,6 +60,15 @@ where
}
}

impl<F, T> fmt::Display for FnPredicate<F, T>
where
F: Fn(&T) -> bool,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}(var)", self.name)
}
}

/// Creates a new predicate that wraps over the given function. The returned
/// type implements `Predicate` and therefore has all combinators available to
/// it.
Expand Down Expand Up @@ -60,6 +97,7 @@ where
{
FnPredicate {
function: function,
name: "fn",
_phantom: PhantomData,
}
}
46 changes: 37 additions & 9 deletions src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! Definition of `Predicate`s for comparisons of membership in a set.

use std::collections::HashSet;
use std::fmt;
use std::hash::Hash;
use std::iter::FromIterator;

Expand All @@ -26,14 +27,14 @@ use Predicate;
#[derive(Debug)]
pub struct InPredicate<T>
where
T: PartialEq,
T: PartialEq + fmt::Debug,
{
inner: Vec<T>,
}

impl<T> InPredicate<T>
where
T: Ord,
T: Ord + fmt::Debug,
{
/// Creates a new predicate that will return `true` when the given `variable` is
/// contained with the set of items provided.
Expand Down Expand Up @@ -64,13 +65,22 @@ where

impl<T> Predicate<T> for InPredicate<T>
where
T: PartialEq,
T: PartialEq + fmt::Debug,
{
fn eval(&self, variable: &T) -> bool {
self.inner.contains(variable)
}
}

impl<T> fmt::Display for InPredicate<T>
where
T: PartialEq + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "var in {:?}", self.inner)
}
}

/// Creates a new predicate that will return `true` when the given `variable` is
/// contained with the set of items provided.
///
Expand Down Expand Up @@ -99,7 +109,7 @@ where
/// ```
pub fn in_iter<I, T>(iter: I) -> InPredicate<T>
where
T: PartialEq,
T: PartialEq + fmt::Debug,
I: IntoIterator<Item = T>,
{
InPredicate {
Expand All @@ -119,20 +129,29 @@ where
#[derive(Debug)]
pub struct OrdInPredicate<T>
where
T: Ord,
T: Ord + fmt::Debug,
{
inner: Vec<T>,
}

impl<T> Predicate<T> for OrdInPredicate<T>
where
T: Ord,
T: Ord + fmt::Debug,
{
fn eval(&self, variable: &T) -> bool {
self.inner.binary_search(variable).is_ok()
}
}

impl<T> fmt::Display for OrdInPredicate<T>
where
T: Ord + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "var in {:?}", self.inner)
}
}

/// Predicate that returns `true` if `variable` is a member of the pre-defined
/// `HashSet`, otherwise returns `false`.
///
Expand All @@ -145,20 +164,29 @@ where
#[derive(Debug)]
pub struct HashableInPredicate<T>
where
T: Hash + Eq,
T: Hash + Eq + fmt::Debug,
{
inner: HashSet<T>,
}

impl<T> Predicate<T> for HashableInPredicate<T>
where
T: Hash + Eq,
T: Hash + Eq + fmt::Debug,
{
fn eval(&self, variable: &T) -> bool {
self.inner.contains(variable)
}
}

impl<T> fmt::Display for HashableInPredicate<T>
where
T: Hash + Eq + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "var in {:?}", self.inner)
}
}

/// Creates a new predicate that will return `true` when the given `variable` is
/// contained with the set of items provided.
///
Expand All @@ -181,7 +209,7 @@ where
/// ```
pub fn in_hash<I, T>(iter: I) -> HashableInPredicate<T>
where
T: Hash + Eq,
T: Hash + Eq + fmt::Debug,
I: IntoIterator<Item = T>,
{
HashableInPredicate {
Expand Down
9 changes: 8 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
//! ```
//! use predicates::prelude::*;
//!
//! use std::fmt;
//!
//! // The simplest predicates are `always()` and `never()`, which always returns
//! // `true` and always returns `false`, respectively. The values are simply
//! // ignored when evaluating against these predicates:
Expand All @@ -57,7 +59,7 @@
//! assert_eq!(false, between_5_and_10.eval(&11));
//! assert_eq!(false, between_5_and_10.eval(&4));
//!
//! // The `Predicate` trait is pretty simple, requiring only the
//! // The `Predicate` trait is pretty simple, the core of it is an
//! // implementation of a `eval` function that takes a single argument and
//! // returns a `bool`. Implementing a custom `Predicate` still allows all the
//! // usual combinators of the `Predicate` trait to work!
Expand All @@ -67,6 +69,11 @@
//! *variable == 42
//! }
//! }
//! impl fmt::Display for IsTheAnswer {
//! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
//! write!(f, "var.is_the_answer()")
//! }
//! }
//!
//! assert_eq!(true, IsTheAnswer.eval(&42));
//! let almost_the_answer = IsTheAnswer.or(predicate::in_iter(vec![41, 43]));
Expand Down
Loading

0 comments on commit 0521670

Please sign in to comment.