From b7d23e13197af51b60ee44e55e364efcc2658f51 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 11 Jul 2018 17:05:16 -0600 Subject: [PATCH 1/7] feat: Add basic reflection Predicates can now report parameter details that don't show up in `Display` as well as what children they have. The expectation is that this will be used by the solution for #7. BREAKING CHANGE: `Predicate`s must also implement `reflection::PredicateReflection`. --- src/boolean.rs | 24 +++++++++++ src/boxed.rs | 7 ++++ src/constant.rs | 3 ++ src/core.rs | 4 +- src/float/close.rs | 3 ++ src/function.rs | 8 ++++ src/iter.rs | 19 +++++++++ src/lib.rs | 6 ++- src/name.rs | 8 ++++ src/ord.rs | 13 ++++++ src/path/existence.rs | 3 ++ src/path/fc.rs | 7 ++++ src/path/fs.rs | 5 +++ src/path/ft.rs | 3 ++ src/reflection.rs | 97 +++++++++++++++++++++++++++++++++++++++++++ src/str/adapters.rs | 13 ++++++ src/str/basics.rs | 11 +++++ src/str/difference.rs | 3 ++ src/str/normalize.rs | 12 +++++- src/str/regex.rs | 5 +++ 20 files changed, 248 insertions(+), 6 deletions(-) create mode 100644 src/reflection.rs diff --git a/src/boolean.rs b/src/boolean.rs index 6b35ca8..addbc74 100644 --- a/src/boolean.rs +++ b/src/boolean.rs @@ -11,6 +11,7 @@ use std::fmt; use std::marker::PhantomData; +use reflection; use Predicate; /// Predicate that combines two `Predicate`s, returning the AND of the results. @@ -55,6 +56,14 @@ where } } +impl reflection::PredicateReflection for AndPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ +} + impl fmt::Display for AndPredicate where M1: Predicate, @@ -108,6 +117,14 @@ where } } +impl reflection::PredicateReflection for OrPredicate +where + M1: Predicate, + M2: Predicate, + Item: ?Sized, +{ +} + impl fmt::Display for OrPredicate where M1: Predicate, @@ -156,6 +173,13 @@ where } } +impl reflection::PredicateReflection for NotPredicate +where + M: Predicate, + Item: ?Sized, +{ +} + impl fmt::Display for NotPredicate where M: Predicate, diff --git a/src/boxed.rs b/src/boxed.rs index 5113b54..fd1f7b1 100644 --- a/src/boxed.rs +++ b/src/boxed.rs @@ -11,6 +11,7 @@ use std::fmt; +use reflection; use Predicate; /// `Predicate` that wraps another `Predicate` as a trait object, allowing @@ -40,6 +41,12 @@ where } } +impl reflection::PredicateReflection for BoxPredicate +where + Item: ?Sized, +{ +} + impl fmt::Display for BoxPredicate where Item: ?Sized, diff --git a/src/constant.rs b/src/constant.rs index f902f8f..da402fe 100644 --- a/src/constant.rs +++ b/src/constant.rs @@ -11,6 +11,7 @@ use std::fmt; use std::marker::PhantomData; +use reflection; use Predicate; /// Predicate that always returns a constant (boolean) result. @@ -28,6 +29,8 @@ impl Predicate for BooleanPredicate { } } +impl reflection::PredicateReflection for BooleanPredicate {} + impl fmt::Display for BooleanPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.retval) diff --git a/src/core.rs b/src/core.rs index dd48e0b..3f60d18 100644 --- a/src/core.rs +++ b/src/core.rs @@ -6,7 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::fmt; +use reflection; /// Trait for generically evaluating a type against a dynamically created /// predicate function. @@ -15,7 +15,7 @@ use std::fmt; /// 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: fmt::Display { +pub trait Predicate: reflection::PredicateReflection { /// Execute this `Predicate` against `variable`, returning the resulting /// boolean. fn eval(&self, variable: &Item) -> bool; diff --git a/src/float/close.rs b/src/float/close.rs index da6e7f3..cc62c62 100644 --- a/src/float/close.rs +++ b/src/float/close.rs @@ -11,6 +11,7 @@ use std::fmt; use float_cmp::ApproxEq; use float_cmp::Ulps; +use reflection; use Predicate; /// Predicate that ensures two numbers are "close" enough, understanding that rounding errors @@ -86,6 +87,8 @@ impl Predicate for IsClosePredicate { } } +impl reflection::PredicateReflection for IsClosePredicate {} + impl fmt::Display for IsClosePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( diff --git a/src/function.rs b/src/function.rs index 36bf793..0f07b1a 100644 --- a/src/function.rs +++ b/src/function.rs @@ -11,6 +11,7 @@ use std::fmt; use std::marker::PhantomData; +use reflection; use Predicate; /// Predicate that wraps a function over a reference that returns a `bool`. @@ -63,6 +64,13 @@ where } } +impl reflection::PredicateReflection for FnPredicate +where + F: Fn(&T) -> bool, + T: ?Sized, +{ +} + impl fmt::Display for FnPredicate where F: Fn(&T) -> bool, diff --git a/src/iter.rs b/src/iter.rs index 7df8007..abbc3e5 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -13,6 +13,7 @@ use std::fmt; use std::hash::Hash; use std::iter::FromIterator; +use reflection; use Predicate; /// Predicate that returns `true` if `variable` is a member of the pre-defined @@ -84,6 +85,12 @@ where } } +impl reflection::PredicateReflection for InPredicate +where + T: PartialEq + fmt::Debug, +{ +} + impl fmt::Display for InPredicate where T: PartialEq + fmt::Debug, @@ -167,6 +174,12 @@ where } } +impl reflection::PredicateReflection for OrdInPredicate +where + T: Ord + fmt::Debug, +{ +} + impl fmt::Display for OrdInPredicate where T: Ord + fmt::Debug, @@ -211,6 +224,12 @@ where } } +impl reflection::PredicateReflection for HashableInPredicate +where + T: Hash + Eq + fmt::Debug, +{ +} + impl fmt::Display for HashableInPredicate where T: Hash + Eq + fmt::Debug, diff --git a/src/lib.rs b/src/lib.rs index 733a2ed..a98561b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,7 @@ //! *variable == 42 //! } //! } +//! impl predicates::reflection::PredicateReflection for IsTheAnswer {} //! impl fmt::Display for IsTheAnswer { //! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { //! write!(f, "var.is_the_answer()") @@ -103,9 +104,10 @@ extern crate regex; pub mod prelude; mod core; -pub use core::Predicate; +pub use core::*; mod boxed; -pub use boxed::BoxPredicate; +pub use boxed::*; +pub mod reflection; // core predicates pub mod constant; diff --git a/src/name.rs b/src/name.rs index 2d1159c..3df0e7c 100644 --- a/src/name.rs +++ b/src/name.rs @@ -11,6 +11,7 @@ use std::fmt; use std::marker::PhantomData; +use reflection; use Predicate; /// Augment an existing predicate with a name. @@ -37,6 +38,13 @@ where } } +impl reflection::PredicateReflection for NamePredicate +where + M: Predicate, + Item: ?Sized, +{ +} + impl fmt::Display for NamePredicate where M: Predicate, diff --git a/src/ord.rs b/src/ord.rs index 1ee560e..2dc809d 100644 --- a/src/ord.rs +++ b/src/ord.rs @@ -10,6 +10,7 @@ use std::fmt; +use reflection; use Predicate; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -65,6 +66,12 @@ where } } +impl reflection::PredicateReflection for EqPredicate +where + T: fmt::Debug + PartialEq, +{ +} + impl fmt::Display for EqPredicate where T: fmt::Debug + PartialEq, @@ -183,6 +190,12 @@ where } } +impl reflection::PredicateReflection for OrdPredicate +where + T: fmt::Debug + PartialOrd, +{ +} + impl fmt::Display for OrdPredicate where T: fmt::Debug + PartialOrd, diff --git a/src/path/existence.rs b/src/path/existence.rs index e99c351..073d56b 100644 --- a/src/path/existence.rs +++ b/src/path/existence.rs @@ -9,6 +9,7 @@ use std::fmt; use std::path; +use reflection; use Predicate; /// Predicate that checks if a file is present @@ -25,6 +26,8 @@ impl Predicate for ExistencePredicate { } } +impl reflection::PredicateReflection for ExistencePredicate {} + impl fmt::Display for ExistencePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}(var)", if self.exists { "exists" } else { "missing" }) diff --git a/src/path/fc.rs b/src/path/fc.rs index 7234bcd..6904eca 100644 --- a/src/path/fc.rs +++ b/src/path/fc.rs @@ -11,6 +11,7 @@ use std::fs; use std::io::{self, Read}; use std::path; +use reflection; use Predicate; fn read_file(path: &path::Path) -> io::Result> { @@ -40,6 +41,12 @@ where } } +impl

reflection::PredicateReflection for FileContentPredicate

+where + P: Predicate<[u8]>, +{ +} + impl

fmt::Display for FileContentPredicate

where P: Predicate<[u8]>, diff --git a/src/path/fs.rs b/src/path/fs.rs index 8ae2160..12c30dc 100644 --- a/src/path/fs.rs +++ b/src/path/fs.rs @@ -11,6 +11,7 @@ use std::fs; use std::io::{self, Read}; use std::path; +use reflection; use Predicate; fn read_file(path: &path::Path) -> io::Result> { @@ -66,6 +67,8 @@ impl Predicate<[u8]> for BinaryFilePredicate { } } +impl reflection::PredicateReflection for BinaryFilePredicate {} + impl fmt::Display for BinaryFilePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "var is {}", self.path.display()) @@ -120,6 +123,8 @@ impl Predicate for StrFilePredicate { } } +impl reflection::PredicateReflection for StrFilePredicate {} + impl fmt::Display for StrFilePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "var is {}", self.path.display()) diff --git a/src/path/ft.rs b/src/path/ft.rs index 1f2dd33..c9bc598 100644 --- a/src/path/ft.rs +++ b/src/path/ft.rs @@ -11,6 +11,7 @@ use std::fs; use std::io; use std::path; +use reflection; use Predicate; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -94,6 +95,8 @@ impl Predicate for FileTypePredicate { } } +impl reflection::PredicateReflection for FileTypePredicate {} + impl fmt::Display for FileTypePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "var is {}", self.ft) diff --git a/src/reflection.rs b/src/reflection.rs new file mode 100644 index 0000000..c6a1c71 --- /dev/null +++ b/src/reflection.rs @@ -0,0 +1,97 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Introspect into the state of a `Predicate`. + +use std::fmt; + +/// Introspect the state of a `Predicate`. +pub trait PredicateReflection: fmt::Display { + /// Parameters of the current `Predicate`. + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![]; + Box::new(params.into_iter()) + } + + /// Nested `Predicate`s of the current `Predicate`. + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![]; + Box::new(params.into_iter()) + } +} + +/// A view of a `Predicate` parameter, provided by reflection. +/// +/// ```rust +/// use predicates; +/// +/// let param = predicates::reflection::Parameter::new("key", &10); +/// println!("{}", param); +/// ``` +pub struct Parameter<'a>(&'a str, &'a fmt::Display); + +impl<'a> Parameter<'a> { + /// Create a new `Parameter`. + pub fn new(key: &'a str, value: &'a fmt::Display) -> Self { + Self { 0: key, 1: value } + } + + /// Access the `Parameter` name. + pub fn name(&self) -> &str { + self.0 + } + + /// Access the `Parameter` value. + pub fn value(&self) -> &fmt::Display { + self.1 + } +} + +impl<'a> fmt::Display for Parameter<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}: {}", self.0, self.1) + } +} + +impl<'a> fmt::Debug for Parameter<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({:?}, {})", self.0, self.1) + } +} + +/// A view of a `Predicate` child, provided by reflection. +pub struct Child<'a>(&'a str, &'a PredicateReflection); + +impl<'a> Child<'a> { + /// Create a new `Predicate` child. + pub fn new(key: &'a str, value: &'a PredicateReflection) -> Self { + Self { 0: key, 1: value } + } + + /// Access the `Child`'s name. + pub fn name(&self) -> &str { + self.0 + } + + /// Access the `Child` `Predicate`. + pub fn value(&self) -> &PredicateReflection { + self.1 + } +} + +impl<'a> fmt::Display for Child<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}: {}", self.0, self.1) + } +} + +impl<'a> fmt::Debug for Child<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({:?}, {})", self.0, self.1) + } +} diff --git a/src/str/adapters.rs b/src/str/adapters.rs index 5ee9026..588b099 100644 --- a/src/str/adapters.rs +++ b/src/str/adapters.rs @@ -10,6 +10,7 @@ use std::ffi; use std::fmt; use std::str; +use reflection; use Predicate; #[cfg(feature = "normalize-line-endings")] use str::normalize::NormalizedPredicate; @@ -34,6 +35,12 @@ where } } +impl

reflection::PredicateReflection for TrimPredicate

+where + P: Predicate, +{ +} + impl

fmt::Display for TrimPredicate

where P: Predicate, @@ -74,6 +81,12 @@ where } } +impl

reflection::PredicateReflection for Utf8Predicate

+where + P: Predicate, +{ +} + impl

fmt::Display for Utf8Predicate

where P: Predicate, diff --git a/src/str/basics.rs b/src/str/basics.rs index 5a31647..0d59a3c 100644 --- a/src/str/basics.rs +++ b/src/str/basics.rs @@ -8,6 +8,7 @@ use std::fmt; +use reflection; use Predicate; /// Predicate that checks for empty strings. @@ -22,6 +23,8 @@ impl Predicate for IsEmptyPredicate { } } +impl reflection::PredicateReflection for IsEmptyPredicate {} + impl fmt::Display for IsEmptyPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "var.is_empty()") @@ -57,6 +60,8 @@ impl Predicate for StartsWithPredicate { } } +impl reflection::PredicateReflection for StartsWithPredicate {} + impl fmt::Display for StartsWithPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "var.starts_with({:?})", self.pattern) @@ -97,6 +102,8 @@ impl Predicate for EndsWithPredicate { } } +impl reflection::PredicateReflection for EndsWithPredicate {} + impl fmt::Display for EndsWithPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "var.ends_with({:?})", self.pattern) @@ -157,6 +164,8 @@ impl Predicate for ContainsPredicate { } } +impl reflection::PredicateReflection for ContainsPredicate {} + impl fmt::Display for ContainsPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "var.contains({:?})", self.pattern) @@ -178,6 +187,8 @@ impl Predicate for MatchesPredicate { } } +impl reflection::PredicateReflection for MatchesPredicate {} + impl fmt::Display for MatchesPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "var.contains({:?}, {})", self.pattern, self.count) diff --git a/src/str/difference.rs b/src/str/difference.rs index 24047f0..c9686e6 100644 --- a/src/str/difference.rs +++ b/src/str/difference.rs @@ -11,6 +11,7 @@ use std::fmt; use difference; +use reflection; use Predicate; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -92,6 +93,8 @@ impl Predicate for DifferencePredicate { } } +impl reflection::PredicateReflection for DifferencePredicate {} + impl fmt::Display for DifferencePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.op { diff --git a/src/str/normalize.rs b/src/str/normalize.rs index bde7f4d..7d8cf5c 100644 --- a/src/str/normalize.rs +++ b/src/str/normalize.rs @@ -6,6 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use reflection; use std::fmt; use Predicate; @@ -23,12 +24,19 @@ where pub(crate) p: P, } +impl

reflection::PredicateReflection for NormalizedPredicate

+where + P: Predicate, +{ +} + impl

Predicate for NormalizedPredicate

where P: Predicate, { fn eval(&self, variable: &str) -> bool { - self.p.eval(&String::from_iter(normalized(variable.chars()))) + self.p + .eval(&String::from_iter(normalized(variable.chars()))) } } @@ -39,4 +47,4 @@ where fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.p) } -} \ No newline at end of file +} diff --git a/src/str/regex.rs b/src/str/regex.rs index f5c1a8e..11e7003 100644 --- a/src/str/regex.rs +++ b/src/str/regex.rs @@ -10,6 +10,7 @@ use std::fmt; use regex; +use reflection; use Predicate; /// An error that occurred during parsing or compiling a regular expression. @@ -46,6 +47,8 @@ impl Predicate for RegexPredicate { } } +impl reflection::PredicateReflection for RegexPredicate {} + impl fmt::Display for RegexPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "var.is_match({})", self.re) @@ -67,6 +70,8 @@ impl Predicate for RegexMatchesPredicate { } } +impl reflection::PredicateReflection for RegexMatchesPredicate {} + impl fmt::Display for RegexMatchesPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "var.is_match({}).count({})", self.re, self.count) From de153eb7bb5043b0887f9aa341e8b2a6d63a3d1c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 11 Jul 2018 17:14:44 -0600 Subject: [PATCH 2/7] feat: Implement reflection for predicates This also saw an audit of predicates to ensure their `Display` would most likely remain as one line of text. Anything in a `Display` that seemed likely to overflow was moved to a `Parameter`. --- src/boolean.rs | 18 +++++++++++++++++ src/boxed.rs | 7 +++++++ src/constant.rs | 7 ++++++- src/float/close.rs | 16 +++++++++------ src/iter.rs | 46 ++++++++++++++++++++++++++++--------------- src/name.rs | 4 ++++ src/path/fc.rs | 4 ++++ src/path/fs.rs | 24 +++++++++++++++------- src/path/ft.rs | 7 ++++++- src/reflection.rs | 35 ++++++++++++++++++++++++++++++++ src/str/adapters.rs | 8 ++++++++ src/str/basics.rs | 9 +++++++-- src/str/difference.rs | 11 ++++++++--- src/str/regex.rs | 9 +++++++-- 14 files changed, 167 insertions(+), 38 deletions(-) diff --git a/src/boolean.rs b/src/boolean.rs index addbc74..9ab6a1e 100644 --- a/src/boolean.rs +++ b/src/boolean.rs @@ -62,6 +62,13 @@ where M2: Predicate, Item: ?Sized, { + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![ + reflection::Child::new("left", &self.a), + reflection::Child::new("right", &self.b), + ]; + Box::new(params.into_iter()) + } } impl fmt::Display for AndPredicate @@ -123,6 +130,13 @@ where M2: Predicate, Item: ?Sized, { + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![ + reflection::Child::new("left", &self.a), + reflection::Child::new("right", &self.b), + ]; + Box::new(params.into_iter()) + } } impl fmt::Display for OrPredicate @@ -178,6 +192,10 @@ where M: Predicate, Item: ?Sized, { + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new("predicate", &self.inner)]; + Box::new(params.into_iter()) + } } impl fmt::Display for NotPredicate diff --git a/src/boxed.rs b/src/boxed.rs index fd1f7b1..de57174 100644 --- a/src/boxed.rs +++ b/src/boxed.rs @@ -45,6 +45,13 @@ impl reflection::PredicateReflection for BoxPredicate where Item: ?Sized, { + fn parameters<'a>(&'a self) -> Box> + 'a> { + self.0.parameters() + } + + fn children<'a>(&'a self) -> Box> + 'a> { + self.0.children() + } } impl fmt::Display for BoxPredicate diff --git a/src/constant.rs b/src/constant.rs index da402fe..29b64ec 100644 --- a/src/constant.rs +++ b/src/constant.rs @@ -29,7 +29,12 @@ impl Predicate for BooleanPredicate { } } -impl reflection::PredicateReflection for BooleanPredicate {} +impl reflection::PredicateReflection for BooleanPredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("value", &self.retval)]; + Box::new(params.into_iter()) + } +} impl fmt::Display for BooleanPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/float/close.rs b/src/float/close.rs index cc62c62..43ff284 100644 --- a/src/float/close.rs +++ b/src/float/close.rs @@ -87,15 +87,19 @@ impl Predicate for IsClosePredicate { } } -impl reflection::PredicateReflection for IsClosePredicate {} +impl reflection::PredicateReflection for IsClosePredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![ + reflection::Parameter::new("epsilon", &self.epsilon), + reflection::Parameter::new("ulps", &self.ulps), + ]; + Box::new(params.into_iter()) + } +} impl fmt::Display for IsClosePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "var ~= {} +/- {} ({})", - self.target, self.epsilon, self.ulps - ) + write!(f, "var ~= {}", self.target) } } diff --git a/src/iter.rs b/src/iter.rs index abbc3e5..9284244 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -30,7 +30,7 @@ pub struct InPredicate where T: PartialEq + fmt::Debug, { - inner: Vec, + inner: reflection::DebugAdapter>, } impl InPredicate @@ -61,9 +61,11 @@ where /// assert_eq!(true, predicate_fn.eval("c")); /// ``` pub fn sort(self) -> OrdInPredicate { - let mut items = self.inner; + let mut items = self.inner.debug; items.sort(); - OrdInPredicate { inner: items } + OrdInPredicate { + inner: reflection::DebugAdapter::new(items), + } } } @@ -72,7 +74,7 @@ where T: PartialEq + fmt::Debug, { fn eval(&self, variable: &T) -> bool { - self.inner.contains(variable) + self.inner.debug.contains(variable) } } @@ -81,7 +83,7 @@ where T: PartialEq + fmt::Debug + ?Sized, { fn eval(&self, variable: &T) -> bool { - self.inner.contains(&variable) + self.inner.debug.contains(&variable) } } @@ -89,6 +91,10 @@ impl reflection::PredicateReflection for InPredicate where T: PartialEq + fmt::Debug, { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("values", &self.inner)]; + Box::new(params.into_iter()) + } } impl fmt::Display for InPredicate @@ -96,7 +102,7 @@ where T: PartialEq + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "var in {:?}", self.inner) + write!(f, "var in values") } } @@ -135,7 +141,7 @@ where I: IntoIterator, { InPredicate { - inner: Vec::from_iter(iter), + inner: reflection::DebugAdapter::new(Vec::from_iter(iter)), } } @@ -153,7 +159,7 @@ pub struct OrdInPredicate where T: Ord + fmt::Debug, { - inner: Vec, + inner: reflection::DebugAdapter>, } impl Predicate for OrdInPredicate @@ -161,7 +167,7 @@ where T: Ord + fmt::Debug, { fn eval(&self, variable: &T) -> bool { - self.inner.binary_search(variable).is_ok() + self.inner.debug.binary_search(variable).is_ok() } } @@ -170,7 +176,7 @@ where T: Ord + fmt::Debug + ?Sized, { fn eval(&self, variable: &T) -> bool { - self.inner.binary_search(&variable).is_ok() + self.inner.debug.binary_search(&variable).is_ok() } } @@ -178,6 +184,10 @@ impl reflection::PredicateReflection for OrdInPredicate where T: Ord + fmt::Debug, { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("values", &self.inner)]; + Box::new(params.into_iter()) + } } impl fmt::Display for OrdInPredicate @@ -185,7 +195,7 @@ where T: Ord + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "var in {:?}", self.inner) + write!(f, "var in values") } } @@ -203,7 +213,7 @@ pub struct HashableInPredicate where T: Hash + Eq + fmt::Debug, { - inner: HashSet, + inner: reflection::DebugAdapter>, } impl Predicate for HashableInPredicate @@ -211,7 +221,7 @@ where T: Hash + Eq + fmt::Debug, { fn eval(&self, variable: &T) -> bool { - self.inner.contains(variable) + self.inner.debug.contains(variable) } } @@ -220,7 +230,7 @@ where T: Hash + Eq + fmt::Debug + ?Sized, { fn eval(&self, variable: &T) -> bool { - self.inner.contains(&variable) + self.inner.debug.contains(&variable) } } @@ -228,6 +238,10 @@ impl reflection::PredicateReflection for HashableInPredicate where T: Hash + Eq + fmt::Debug, { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("values", &self.inner)]; + Box::new(params.into_iter()) + } } impl fmt::Display for HashableInPredicate @@ -235,7 +249,7 @@ where T: Hash + Eq + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "var in {:?}", self.inner) + write!(f, "var in values") } } @@ -268,6 +282,6 @@ where I: IntoIterator, { HashableInPredicate { - inner: HashSet::from_iter(iter), + inner: reflection::DebugAdapter::new(HashSet::from_iter(iter)), } } diff --git a/src/name.rs b/src/name.rs index 3df0e7c..92f53b8 100644 --- a/src/name.rs +++ b/src/name.rs @@ -43,6 +43,10 @@ where M: Predicate, Item: ?Sized, { + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new(self.name, &self.inner)]; + Box::new(params.into_iter()) + } } impl fmt::Display for NamePredicate diff --git a/src/path/fc.rs b/src/path/fc.rs index 6904eca..65acef8 100644 --- a/src/path/fc.rs +++ b/src/path/fc.rs @@ -45,6 +45,10 @@ impl

reflection::PredicateReflection for FileContentPredicate

where P: Predicate<[u8]>, { + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new("predicate", &self.p)]; + Box::new(params.into_iter()) + } } impl

fmt::Display for FileContentPredicate

diff --git a/src/path/fs.rs b/src/path/fs.rs index 12c30dc..0c01ab2 100644 --- a/src/path/fs.rs +++ b/src/path/fs.rs @@ -24,13 +24,13 @@ fn read_file(path: &path::Path) -> io::Result> { #[derive(Debug, Clone, PartialEq, Eq)] pub struct BinaryFilePredicate { path: path::PathBuf, - content: Vec, + content: reflection::DebugAdapter>, } impl BinaryFilePredicate { fn eval(&self, path: &path::Path) -> io::Result { let content = read_file(path)?; - Ok(self.content == content) + Ok(self.content.debug == content) } /// Creates a new `Predicate` that ensures complete equality @@ -50,7 +50,7 @@ impl BinaryFilePredicate { /// ``` pub fn utf8(self) -> Option { let path = self.path; - let content = String::from_utf8(self.content).ok()?; + let content = String::from_utf8(self.content.debug).ok()?; Some(StrFilePredicate { path, content }) } } @@ -63,11 +63,16 @@ impl Predicate for BinaryFilePredicate { impl Predicate<[u8]> for BinaryFilePredicate { fn eval(&self, actual: &[u8]) -> bool { - self.content == actual + self.content.debug == actual } } -impl reflection::PredicateReflection for BinaryFilePredicate {} +impl reflection::PredicateReflection for BinaryFilePredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("content", &self.content)]; + Box::new(params.into_iter()) + } +} impl fmt::Display for BinaryFilePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -89,7 +94,7 @@ impl fmt::Display for BinaryFilePredicate { /// assert_eq!(false, predicate_file.eval(Path::new("Cargo.lock"))); /// ``` pub fn eq_file(path: &path::Path) -> BinaryFilePredicate { - let content = read_file(path).unwrap(); + let content = reflection::DebugAdapter::new(read_file(path).unwrap()); BinaryFilePredicate { path: path.to_path_buf(), content, @@ -123,7 +128,12 @@ impl Predicate for StrFilePredicate { } } -impl reflection::PredicateReflection for StrFilePredicate {} +impl reflection::PredicateReflection for StrFilePredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("content", &self.content)]; + Box::new(params.into_iter()) + } +} impl fmt::Display for StrFilePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/path/ft.rs b/src/path/ft.rs index c9bc598..7d829a6 100644 --- a/src/path/ft.rs +++ b/src/path/ft.rs @@ -95,7 +95,12 @@ impl Predicate for FileTypePredicate { } } -impl reflection::PredicateReflection for FileTypePredicate {} +impl reflection::PredicateReflection for FileTypePredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("follow", &self.follow)]; + Box::new(params.into_iter()) + } +} impl fmt::Display for FileTypePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/reflection.rs b/src/reflection.rs index c6a1c71..395b970 100644 --- a/src/reflection.rs +++ b/src/reflection.rs @@ -95,3 +95,38 @@ impl<'a> fmt::Debug for Child<'a> { write!(f, "({:?}, {})", self.0, self.1) } } + +#[derive(Clone, PartialEq, Eq)] +pub(crate) struct DebugAdapter +where + T: fmt::Debug, +{ + pub(crate) debug: T, +} + +impl DebugAdapter +where + T: fmt::Debug, +{ + pub fn new(debug: T) -> Self { + Self { debug } + } +} + +impl fmt::Display for DebugAdapter +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:#?}", self.debug) + } +} + +impl fmt::Debug for DebugAdapter +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.debug.fmt(f) + } +} diff --git a/src/str/adapters.rs b/src/str/adapters.rs index 588b099..8a20531 100644 --- a/src/str/adapters.rs +++ b/src/str/adapters.rs @@ -39,6 +39,10 @@ impl

reflection::PredicateReflection for TrimPredicate

where P: Predicate, { + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new("predicate", &self.p)]; + Box::new(params.into_iter()) + } } impl

fmt::Display for TrimPredicate

@@ -85,6 +89,10 @@ impl

reflection::PredicateReflection for Utf8Predicate

where P: Predicate, { + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new("predicate", &self.p)]; + Box::new(params.into_iter()) + } } impl

fmt::Display for Utf8Predicate

diff --git a/src/str/basics.rs b/src/str/basics.rs index 0d59a3c..493bd8d 100644 --- a/src/str/basics.rs +++ b/src/str/basics.rs @@ -187,11 +187,16 @@ impl Predicate for MatchesPredicate { } } -impl reflection::PredicateReflection for MatchesPredicate {} +impl reflection::PredicateReflection for MatchesPredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("count", &self.count)]; + Box::new(params.into_iter()) + } +} impl fmt::Display for MatchesPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "var.contains({:?}, {})", self.pattern, self.count) + write!(f, "var.contains({})", self.pattern) } } diff --git a/src/str/difference.rs b/src/str/difference.rs index c9686e6..e0ff91b 100644 --- a/src/str/difference.rs +++ b/src/str/difference.rs @@ -93,13 +93,18 @@ impl Predicate for DifferencePredicate { } } -impl reflection::PredicateReflection for DifferencePredicate {} +impl reflection::PredicateReflection for DifferencePredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("original", &self.orig)]; + Box::new(params.into_iter()) + } +} impl fmt::Display for DifferencePredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.op { - DistanceOp::Similar => write!(f, "var - {:?} <= {}", self.orig, self.distance), - DistanceOp::Different => write!(f, "{} < var - {:?}", self.distance, self.orig), + DistanceOp::Similar => write!(f, "var - original <= {}", self.distance), + DistanceOp::Different => write!(f, "{} < var - original", self.distance), } } } diff --git a/src/str/regex.rs b/src/str/regex.rs index 11e7003..3462337 100644 --- a/src/str/regex.rs +++ b/src/str/regex.rs @@ -70,11 +70,16 @@ impl Predicate for RegexMatchesPredicate { } } -impl reflection::PredicateReflection for RegexMatchesPredicate {} +impl reflection::PredicateReflection for RegexMatchesPredicate { + fn parameters<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Parameter::new("count", &self.count)]; + Box::new(params.into_iter()) + } +} impl fmt::Display for RegexMatchesPredicate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "var.is_match({}).count({})", self.re, self.count) + write!(f, "var.is_match({})", self.re) } } From 940f320020af519611c85c58d21e45fd809841e8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 28 Jul 2018 16:52:17 -0600 Subject: [PATCH 3/7] 8728 norm full --- src/str/normalize.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/str/normalize.rs b/src/str/normalize.rs index 7d8cf5c..02b8ff1 100644 --- a/src/str/normalize.rs +++ b/src/str/normalize.rs @@ -28,6 +28,10 @@ impl

reflection::PredicateReflection for NormalizedPredicate

where P: Predicate, { + fn children<'a>(&'a self) -> Box> + 'a> { + let params = vec![reflection::Child::new("predicate", &self.p)]; + Box::new(params.into_iter()) + } } impl

Predicate for NormalizedPredicate

From 53e47ffbec779edf71ea9dcccdb2a883816c084c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 11 Jul 2018 17:30:17 -0600 Subject: [PATCH 4/7] feat: Extend Predicate to report cause This is the last of the foundation for resolving #7. --- src/core.rs | 13 ++++ src/reflection.rs | 162 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+) diff --git a/src/core.rs b/src/core.rs index 3f60d18..f04b715 100644 --- a/src/core.rs +++ b/src/core.rs @@ -19,4 +19,17 @@ pub trait Predicate: reflection::PredicateReflection { /// Execute this `Predicate` against `variable`, returning the resulting /// boolean. fn eval(&self, variable: &Item) -> bool; + + /// Find a case that proves this predicate as `expected` when run against `variable`. + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> + where + Self: Sized, + { + let actual = self.eval(variable); + if expected == actual { + Some(reflection::Case::new(Some(self), actual)) + } else { + None + } + } } diff --git a/src/reflection.rs b/src/reflection.rs index 395b970..56974e3 100644 --- a/src/reflection.rs +++ b/src/reflection.rs @@ -8,7 +8,9 @@ //! Introspect into the state of a `Predicate`. +use std::borrow; use std::fmt; +use std::slice; /// Introspect the state of a `Predicate`. pub trait PredicateReflection: fmt::Display { @@ -96,6 +98,166 @@ impl<'a> fmt::Debug for Child<'a> { } } +/// A descriptive explanation for why a predicate failed. +pub struct Case<'a> { + predicate: Option<&'a PredicateReflection>, + result: bool, + products: Vec, + children: Vec>, +} + +impl<'a> Case<'a> { + /// Create a new `Case` describing the result of a `Predicate`. + pub fn new(predicate: Option<&'a PredicateReflection>, result: bool) -> Self { + Self { + predicate, + result, + products: Default::default(), + children: Default::default(), + } + } + + /// Add an additional by product to a `Case`. + pub fn add_product(mut self, product: Product) -> Self { + self.products.push(product); + self + } + + /// Add an additional by product to a `Case`. + pub fn add_child(mut self, child: Case<'a>) -> Self { + self.children.push(child); + self + } + + /// The `Predicate` that produced this case. + pub fn predicate(&self) -> Option<&PredicateReflection> { + self.predicate + } + + /// The result of this case. + pub fn result(&self) -> bool { + self.result + } + + /// Access the by-products from determining this case. + pub fn products(&self) -> CaseProducts { + CaseProducts { + 0: self.products.iter(), + } + } + + /// Access the sub-cases. + pub fn children(&self) -> CaseChildren { + CaseChildren { + 0: self.children.iter(), + } + } +} + +impl<'a> fmt::Debug for Case<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let predicate = if let Some(ref predicate) = self.predicate { + format!("Some({})", predicate) + } else { + "None".to_owned() + }; + f.debug_struct("Case") + .field("predicate", &predicate) + .field("result", &self.result) + .field("products", &self.products) + .field("children", &self.children) + .finish() + } +} + +/// Iterator over a `Case`s by-products. +#[derive(Debug, Clone)] +pub struct CaseProducts<'a>(slice::Iter<'a, Product>); + +impl<'a> Iterator for CaseProducts<'a> { + type Item = &'a Product; + + fn next(&mut self) -> Option<&'a Product> { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize { + self.0.count() + } +} + +/// Iterator over a `Case`s sub-cases. +#[derive(Debug, Clone)] +pub struct CaseChildren<'a>(slice::Iter<'a, Case<'a>>); + +impl<'a> Iterator for CaseChildren<'a> { + type Item = &'a Case<'a>; + + fn next(&mut self) -> Option<&'a Case<'a>> { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize { + self.0.count() + } +} + +/// A by-product of a predicate evaluation. +/// +/// ```rust +/// use predicates; +/// +/// let product = predicates::reflection::Product::new("key", "value"); +/// println!("{}", product); +/// let product = predicates::reflection::Product::new(format!("key-{}", 5), 30); +/// println!("{}", product); +/// ``` +pub struct Product(borrow::Cow<'static, str>, Box); + +impl Product { + /// Create a new `Product`. + pub fn new(key: S, value: D) -> Self + where + S: Into>, + D: fmt::Display + 'static, + { + Self { + 0: key.into(), + 1: Box::new(value), + } + } + + /// Access the `Product` name. + pub fn name(&self) -> &str { + self.0.as_ref() + } + + /// Access the `Product` value. + pub fn value(&self) -> &fmt::Display { + &self.1 + } +} + +impl<'a> fmt::Display for Product { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}: {}", self.0, self.1) + } +} + +impl<'a> fmt::Debug for Product { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({:?}, {})", self.0, self.1) + } +} + #[derive(Clone, PartialEq, Eq)] pub(crate) struct DebugAdapter where From 200b8dedf079d8af6bf0cf00909c373aa93a4e18 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 12 Jul 2018 18:47:22 -0600 Subject: [PATCH 5/7] feat: Implement `find_case` for predicates A non-default implementation is provided for all predicates that can add additional information than what `eval` / `Display` provide. Exceptions - `eq_file`: I want to re-work this predicate - `BoxPredicate`: The where clause is causing problems This is the information needed for resolving #7. Now, rendering is the only thing left. --- src/boolean.rs | 212 ++++++++++++++++++++++++++++++++++++++++++ src/float/close.rs | 19 ++++ src/name.rs | 4 + src/path/fc.rs | 18 ++++ src/path/ft.rs | 36 ++++++- src/str/adapters.rs | 41 +++++++- src/str/basics.rs | 13 +++ src/str/difference.rs | 14 +++ src/str/normalize.rs | 5 + src/str/regex.rs | 13 +++ 10 files changed, 369 insertions(+), 6 deletions(-) diff --git a/src/boolean.rs b/src/boolean.rs index 9ab6a1e..9936611 100644 --- a/src/boolean.rs +++ b/src/boolean.rs @@ -54,6 +54,24 @@ where fn eval(&self, item: &Item) -> bool { self.a.eval(item) && self.b.eval(item) } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + let child_a = self.a.find_case(expected, variable); + match (expected, child_a) { + (true, Some(child_a)) => self.b.find_case(expected, variable).map(|child_b| { + reflection::Case::new(Some(self), expected) + .add_child(child_a) + .add_child(child_b) + }), + (true, None) => None, + (false, Some(child_a)) => { + Some(reflection::Case::new(Some(self), expected).add_child(child_a)) + } + (false, None) => self.b + .find_case(expected, variable) + .map(|child_b| reflection::Case::new(Some(self), expected).add_child(child_b)), + } + } } impl reflection::PredicateReflection for AndPredicate @@ -82,6 +100,91 @@ where } } +#[cfg(test)] +mod test_and { + use prelude::*; + + #[test] + fn find_case_true() { + assert!( + predicate::always() + .and(predicate::always()) + .find_case(true, &5) + .is_some() + ); + } + + #[test] + fn find_case_true_left_fail() { + assert!( + predicate::never() + .and(predicate::always()) + .find_case(true, &5) + .is_none() + ); + } + + #[test] + fn find_case_true_right_fail() { + assert!( + predicate::always() + .and(predicate::never()) + .find_case(true, &5) + .is_none() + ); + } + + #[test] + fn find_case_true_fails() { + assert!( + predicate::never() + .and(predicate::never()) + .find_case(true, &5) + .is_none() + ); + } + + #[test] + fn find_case_false() { + assert!( + predicate::never() + .and(predicate::never()) + .find_case(false, &5) + .is_some() + ); + } + + #[test] + fn find_case_false_fails() { + assert!( + predicate::always() + .and(predicate::always()) + .find_case(false, &5) + .is_none() + ); + } + + #[test] + fn find_case_false_left_fail() { + assert!( + predicate::never() + .and(predicate::always()) + .find_case(false, &5) + .is_some() + ); + } + + #[test] + fn find_case_false_right_fail() { + assert!( + predicate::always() + .and(predicate::never()) + .find_case(false, &5) + .is_some() + ); + } +} + /// Predicate that combines two `Predicate`s, returning the OR of the results. /// /// This is created by the `Predicate::or` function. @@ -122,6 +225,24 @@ where fn eval(&self, item: &Item) -> bool { self.a.eval(item) || self.b.eval(item) } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + let child_a = self.a.find_case(expected, variable); + match (expected, child_a) { + (true, Some(child_a)) => { + Some(reflection::Case::new(Some(self), expected).add_child(child_a)) + } + (true, None) => self.b + .find_case(expected, variable) + .map(|child_b| reflection::Case::new(Some(self), expected).add_child(child_b)), + (false, Some(child_a)) => self.b.find_case(expected, variable).map(|child_b| { + reflection::Case::new(Some(self), expected) + .add_child(child_a) + .add_child(child_b) + }), + (false, None) => None, + } + } } impl reflection::PredicateReflection for OrPredicate @@ -150,6 +271,91 @@ where } } +#[cfg(test)] +mod test_or { + use prelude::*; + + #[test] + fn find_case_true() { + assert!( + predicate::always() + .or(predicate::always()) + .find_case(true, &5) + .is_some() + ); + } + + #[test] + fn find_case_true_left_fail() { + assert!( + predicate::never() + .or(predicate::always()) + .find_case(true, &5) + .is_some() + ); + } + + #[test] + fn find_case_true_right_fail() { + assert!( + predicate::always() + .or(predicate::never()) + .find_case(true, &5) + .is_some() + ); + } + + #[test] + fn find_case_true_fails() { + assert!( + predicate::never() + .or(predicate::never()) + .find_case(true, &5) + .is_none() + ); + } + + #[test] + fn find_case_false() { + assert!( + predicate::never() + .or(predicate::never()) + .find_case(false, &5) + .is_some() + ); + } + + #[test] + fn find_case_false_fails() { + assert!( + predicate::always() + .or(predicate::always()) + .find_case(false, &5) + .is_none() + ); + } + + #[test] + fn find_case_false_left_fail() { + assert!( + predicate::never() + .or(predicate::always()) + .find_case(false, &5) + .is_none() + ); + } + + #[test] + fn find_case_false_right_fail() { + assert!( + predicate::always() + .or(predicate::never()) + .find_case(false, &5) + .is_none() + ); + } +} + /// Predicate that returns a `Predicate` taking the logical NOT of the result. /// /// This is created by the `Predicate::not` function. @@ -185,6 +391,12 @@ where fn eval(&self, item: &Item) -> bool { !self.inner.eval(item) } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + self.inner + .find_case(!expected, variable) + .map(|child| reflection::Case::new(Some(self), expected).add_child(child)) + } } impl reflection::PredicateReflection for NotPredicate diff --git a/src/float/close.rs b/src/float/close.rs index 43ff284..7c19697 100644 --- a/src/float/close.rs +++ b/src/float/close.rs @@ -85,6 +85,25 @@ impl Predicate for IsClosePredicate { fn eval(&self, variable: &f64) -> bool { variable.approx_eq(&self.target, self.epsilon, self.ulps) } + + fn find_case<'a>(&'a self, expected: bool, variable: &f64) -> Option> { + let actual = self.eval(variable); + if expected == actual { + Some( + reflection::Case::new(Some(self), actual) + .add_product(reflection::Product::new( + "actual epsilon", + (variable - self.target).abs(), + )) + .add_product(reflection::Product::new( + "actual ulps", + variable.ulps(&self.target).abs(), + )), + ) + } else { + None + } + } } impl reflection::PredicateReflection for IsClosePredicate { diff --git a/src/name.rs b/src/name.rs index 92f53b8..7d76283 100644 --- a/src/name.rs +++ b/src/name.rs @@ -36,6 +36,10 @@ where fn eval(&self, item: &Item) -> bool { self.inner.eval(item) } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + self.inner.find_case(expected, variable) + } } impl reflection::PredicateReflection for NamePredicate diff --git a/src/path/fc.rs b/src/path/fc.rs index 65acef8..462d63e 100644 --- a/src/path/fc.rs +++ b/src/path/fc.rs @@ -67,6 +67,24 @@ where fn eval(&self, path: &path::Path) -> bool { self.eval(path).unwrap_or(false) } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &path::Path, + ) -> Option> { + let buffer = read_file(variable); + match (expected, buffer) { + (_, Ok(buffer)) => self.p + .find_case(expected, &buffer) + .map(|child| reflection::Case::new(Some(self), expected).add_child(child)), + (true, Err(_)) => None, + (false, Err(err)) => Some( + reflection::Case::new(Some(self), false) + .add_product(reflection::Product::new("error", err)), + ), + } + } } /// `Predicate` extension adapting a `slice` Predicate. diff --git a/src/path/ft.rs b/src/path/ft.rs index 7d829a6..9f8b95f 100644 --- a/src/path/ft.rs +++ b/src/path/ft.rs @@ -22,8 +22,12 @@ enum FileType { } impl FileType { - fn from_path(path: &path::Path) -> io::Result { - let file_type = path.metadata()?.file_type(); + fn from_path(path: &path::Path, follow: bool) -> io::Result { + let file_type = if follow { + path.metadata() + } else { + path.symlink_metadata() + }?.file_type(); if file_type.is_dir() { return Ok(FileType::Dir); } @@ -76,7 +80,7 @@ impl FileTypePredicate { /// Allow to create an `FileTypePredicate` from a `path` pub fn from_path(path: &path::Path) -> io::Result { Ok(FileTypePredicate { - ft: FileType::from_path(path)?, + ft: FileType::from_path(path, true)?, follow: true, }) } @@ -93,6 +97,32 @@ impl Predicate for FileTypePredicate { .map(|m| self.ft.eval(m.file_type())) .unwrap_or(false) } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &path::Path, + ) -> Option> { + let actual_type = FileType::from_path(variable, self.follow); + match (expected, actual_type) { + (_, Ok(actual_type)) => { + let result = self.ft == actual_type; + if result == expected { + Some( + reflection::Case::new(Some(self), result) + .add_product(reflection::Product::new("actual filetype", actual_type)), + ) + } else { + None + } + } + (true, Err(_)) => None, + (false, Err(err)) => Some( + reflection::Case::new(Some(self), false) + .add_product(reflection::Product::new("error", err)), + ), + } + } } impl reflection::PredicateReflection for FileTypePredicate { diff --git a/src/str/adapters.rs b/src/str/adapters.rs index 8a20531..aaddbfb 100644 --- a/src/str/adapters.rs +++ b/src/str/adapters.rs @@ -11,9 +11,9 @@ use std::fmt; use std::str; use reflection; -use Predicate; #[cfg(feature = "normalize-line-endings")] use str::normalize::NormalizedPredicate; +use Predicate; /// Predicate adaper that trims the variable being tested. /// @@ -33,6 +33,10 @@ where fn eval(&self, variable: &str) -> bool { self.p.eval(variable.trim()) } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + self.p.find_case(expected, variable.trim()) + } } impl

reflection::PredicateReflection for TrimPredicate

@@ -72,6 +76,24 @@ where fn eval(&self, variable: &ffi::OsStr) -> bool { variable.to_str().map(|s| self.p.eval(s)).unwrap_or(false) } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &ffi::OsStr, + ) -> Option> { + let var_str = variable.to_str(); + match (expected, var_str) { + (_, Some(var_str)) => self.p.find_case(expected, var_str).map(|child| { + child.add_product(reflection::Product::new("var as str", var_str.to_owned())) + }), + (true, None) => None, + (false, None) => Some( + reflection::Case::new(Some(self), false) + .add_product(reflection::Product::new("error", "Invalid UTF-8 string")), + ), + } + } } impl

Predicate<[u8]> for Utf8Predicate

@@ -83,6 +105,20 @@ where .map(|s| self.p.eval(s)) .unwrap_or(false) } + + fn find_case<'a>(&'a self, expected: bool, variable: &[u8]) -> Option> { + let var_str = str::from_utf8(variable); + match (expected, var_str) { + (_, Ok(var_str)) => self.p.find_case(expected, var_str).map(|child| { + child.add_product(reflection::Product::new("var as str", var_str.to_owned())) + }), + (true, Err(_)) => None, + (false, Err(err)) => Some( + reflection::Case::new(Some(self), false) + .add_product(reflection::Product::new("error", err)), + ), + } + } } impl

reflection::PredicateReflection for Utf8Predicate

@@ -150,7 +186,7 @@ where /// /// ``` /// use predicates::prelude::*; - /// + /// /// let predicate_fn = predicate::eq("Hello World!\n").normalize(); /// assert_eq!(true, predicate_fn.eval("Hello World!\n")); /// assert_eq!(true, predicate_fn.eval("Hello World!\r")); @@ -162,7 +198,6 @@ where fn normalize(self) -> NormalizedPredicate { NormalizedPredicate { p: self } } - } impl

PredicateStrExt for P diff --git a/src/str/basics.rs b/src/str/basics.rs index 493bd8d..1e68600 100644 --- a/src/str/basics.rs +++ b/src/str/basics.rs @@ -185,6 +185,19 @@ impl Predicate for MatchesPredicate { fn eval(&self, variable: &str) -> bool { variable.matches(&self.pattern).count() == self.count } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + let actual_count = variable.matches(&self.pattern).count(); + let result = self.count == actual_count; + if result == expected { + Some( + reflection::Case::new(Some(self), result) + .add_product(reflection::Product::new("actual count", actual_count)), + ) + } else { + None + } + } } impl reflection::PredicateReflection for MatchesPredicate { diff --git a/src/str/difference.rs b/src/str/difference.rs index e0ff91b..59893b4 100644 --- a/src/str/difference.rs +++ b/src/str/difference.rs @@ -91,6 +91,20 @@ impl Predicate for DifferencePredicate { let change = difference::Changeset::new(&self.orig, edit, &self.split); self.op.eval(self.distance, change.distance) } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + let change = difference::Changeset::new(&self.orig, variable, &self.split); + let result = self.op.eval(self.distance, change.distance); + if result == expected { + Some( + reflection::Case::new(Some(self), result) + .add_product(reflection::Product::new("actual distance", change.distance)) + .add_product(reflection::Product::new("diff", change)), + ) + } else { + None + } + } } impl reflection::PredicateReflection for DifferencePredicate { diff --git a/src/str/normalize.rs b/src/str/normalize.rs index 02b8ff1..3d92b7e 100644 --- a/src/str/normalize.rs +++ b/src/str/normalize.rs @@ -42,6 +42,11 @@ where self.p .eval(&String::from_iter(normalized(variable.chars()))) } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + let variable = String::from_iter(normalized(variable.chars())); + self.p.find_case(expected, &variable) + } } impl

fmt::Display for NormalizedPredicate

diff --git a/src/str/regex.rs b/src/str/regex.rs index 3462337..12ab8cc 100644 --- a/src/str/regex.rs +++ b/src/str/regex.rs @@ -68,6 +68,19 @@ impl Predicate for RegexMatchesPredicate { fn eval(&self, variable: &str) -> bool { self.re.find_iter(variable).count() == self.count } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + let actual_count = self.re.find_iter(variable).count(); + let result = self.count == actual_count; + if result == expected { + Some( + reflection::Case::new(Some(self), result) + .add_product(reflection::Product::new("actual count", actual_count)), + ) + } else { + None + } + } } impl reflection::PredicateReflection for RegexMatchesPredicate { From 1e92602966b87c1a702f1cfb0bb2e9392db0cab3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 19 Jul 2018 21:03:04 -0600 Subject: [PATCH 6/7] chore: Make `find_case` object safe Apparently, a provided function can't downcast `&self` but I can if I spread the implementation around. Overall, trying to meet the goal of making it simple to write a Predicate. Anyone besides us will need to copy `default_find_case` if they want the reference. Not sure what would be a good way to make that good enough for being stable. --- src/boxed.rs | 5 +++++ src/constant.rs | 5 +++++ src/core.rs | 24 +++++++++++++++++++----- src/function.rs | 5 +++++ src/iter.rs | 25 +++++++++++++++++++++++++ src/ord.rs | 17 +++++++++++++++++ src/path/existence.rs | 9 +++++++++ src/path/fs.rs | 25 +++++++++++++++++++++++++ src/str/basics.rs | 17 +++++++++++++++++ src/str/regex.rs | 5 +++++ 10 files changed, 132 insertions(+), 5 deletions(-) diff --git a/src/boxed.rs b/src/boxed.rs index de57174..c7dda6c 100644 --- a/src/boxed.rs +++ b/src/boxed.rs @@ -11,6 +11,7 @@ use std::fmt; +use core; use reflection; use Predicate; @@ -70,6 +71,10 @@ where fn eval(&self, variable: &Item) -> bool { self.0.eval(variable) } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + core::default_find_case(self, expected, variable) + } } /// `Predicate` extension for boxing a `Predicate`. diff --git a/src/constant.rs b/src/constant.rs index 29b64ec..76bad7e 100644 --- a/src/constant.rs +++ b/src/constant.rs @@ -11,6 +11,7 @@ use std::fmt; use std::marker::PhantomData; +use core; use reflection; use Predicate; @@ -27,6 +28,10 @@ impl Predicate for BooleanPredicate { fn eval(&self, _variable: &Item) -> bool { self.retval } + + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for BooleanPredicate { diff --git a/src/core.rs b/src/core.rs index f04b715..b07ec61 100644 --- a/src/core.rs +++ b/src/core.rs @@ -21,15 +21,29 @@ pub trait Predicate: reflection::PredicateReflection { fn eval(&self, variable: &Item) -> bool; /// Find a case that proves this predicate as `expected` when run against `variable`. - fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> - where - Self: Sized, - { + fn find_case<'a>(&'a self, expected: bool, variable: &Item) -> Option> { let actual = self.eval(variable); if expected == actual { - Some(reflection::Case::new(Some(self), actual)) + Some(reflection::Case::new(None, actual)) } else { None } } } + +pub(crate) fn default_find_case<'a, P, Item>( + pred: &'a P, + expected: bool, + variable: &Item, +) -> Option> +where + P: Predicate, + Item: ?Sized, +{ + let actual = pred.eval(variable); + if expected == actual { + Some(reflection::Case::new(Some(pred), actual)) + } else { + None + } +} diff --git a/src/function.rs b/src/function.rs index 0f07b1a..c83de14 100644 --- a/src/function.rs +++ b/src/function.rs @@ -11,6 +11,7 @@ use std::fmt; use std::marker::PhantomData; +use core; use reflection; use Predicate; @@ -62,6 +63,10 @@ where fn eval(&self, variable: &T) -> bool { (self.function)(variable) } + + fn find_case<'a>(&'a self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for FnPredicate diff --git a/src/iter.rs b/src/iter.rs index 9284244..5ccd3af 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -13,6 +13,7 @@ use std::fmt; use std::hash::Hash; use std::iter::FromIterator; +use core; use reflection; use Predicate; @@ -76,6 +77,10 @@ where fn eval(&self, variable: &T) -> bool { self.inner.debug.contains(variable) } + + fn find_case<'a>(&'a self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl<'a, T> Predicate for InPredicate<&'a T> @@ -85,6 +90,10 @@ where fn eval(&self, variable: &T) -> bool { self.inner.debug.contains(&variable) } + + fn find_case<'b>(&'b self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for InPredicate @@ -169,6 +178,10 @@ where fn eval(&self, variable: &T) -> bool { self.inner.debug.binary_search(variable).is_ok() } + + fn find_case<'a>(&'a self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl<'a, T> Predicate for OrdInPredicate<&'a T> @@ -178,6 +191,10 @@ where fn eval(&self, variable: &T) -> bool { self.inner.debug.binary_search(&variable).is_ok() } + + fn find_case<'b>(&'b self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for OrdInPredicate @@ -223,6 +240,10 @@ where fn eval(&self, variable: &T) -> bool { self.inner.debug.contains(variable) } + + fn find_case<'a>(&'a self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl<'a, T> Predicate for HashableInPredicate<&'a T> @@ -232,6 +253,10 @@ where fn eval(&self, variable: &T) -> bool { self.inner.debug.contains(&variable) } + + fn find_case<'b>(&'b self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for HashableInPredicate diff --git a/src/ord.rs b/src/ord.rs index 2dc809d..fa52c24 100644 --- a/src/ord.rs +++ b/src/ord.rs @@ -10,6 +10,7 @@ use std::fmt; +use core; use reflection; use Predicate; @@ -52,6 +53,10 @@ where EqOps::NotEqual => variable.ne(&self.constant), } } + + fn find_case<'a>(&'a self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl<'a, T> Predicate for EqPredicate<&'a T> @@ -64,6 +69,10 @@ where EqOps::NotEqual => variable.ne(self.constant), } } + + fn find_case<'b>(&'b self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for EqPredicate @@ -174,6 +183,10 @@ where OrdOps::GreaterThan => variable.gt(&self.constant), } } + + fn find_case<'a>(&'a self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl<'a, T> Predicate for OrdPredicate<&'a T> @@ -188,6 +201,10 @@ where OrdOps::GreaterThan => variable.gt(self.constant), } } + + fn find_case<'b>(&'b self, expected: bool, variable: &T) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for OrdPredicate diff --git a/src/path/existence.rs b/src/path/existence.rs index 073d56b..43f537b 100644 --- a/src/path/existence.rs +++ b/src/path/existence.rs @@ -9,6 +9,7 @@ use std::fmt; use std::path; +use core; use reflection; use Predicate; @@ -24,6 +25,14 @@ impl Predicate for ExistencePredicate { fn eval(&self, path: &path::Path) -> bool { path.exists() == self.exists } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &path::Path, + ) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for ExistencePredicate {} diff --git a/src/path/fs.rs b/src/path/fs.rs index 0c01ab2..34ae601 100644 --- a/src/path/fs.rs +++ b/src/path/fs.rs @@ -11,6 +11,7 @@ use std::fs; use std::io::{self, Read}; use std::path; +use core; use reflection; use Predicate; @@ -59,12 +60,24 @@ impl Predicate for BinaryFilePredicate { fn eval(&self, path: &path::Path) -> bool { self.eval(path).unwrap_or(false) } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &path::Path, + ) -> Option> { + core::default_find_case(self, expected, variable) + } } impl Predicate<[u8]> for BinaryFilePredicate { fn eval(&self, actual: &[u8]) -> bool { self.content.debug == actual } + + fn find_case<'a>(&'a self, expected: bool, variable: &[u8]) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for BinaryFilePredicate { @@ -120,12 +133,24 @@ impl Predicate for StrFilePredicate { fn eval(&self, path: &path::Path) -> bool { self.eval(path).unwrap_or(false) } + + fn find_case<'a>( + &'a self, + expected: bool, + variable: &path::Path, + ) -> Option> { + core::default_find_case(self, expected, variable) + } } impl Predicate for StrFilePredicate { fn eval(&self, actual: &str) -> bool { self.content == actual } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for StrFilePredicate { diff --git a/src/str/basics.rs b/src/str/basics.rs index 1e68600..dd13ddc 100644 --- a/src/str/basics.rs +++ b/src/str/basics.rs @@ -8,6 +8,7 @@ use std::fmt; +use core; use reflection; use Predicate; @@ -21,6 +22,10 @@ impl Predicate for IsEmptyPredicate { fn eval(&self, variable: &str) -> bool { variable.is_empty() } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for IsEmptyPredicate {} @@ -58,6 +63,10 @@ impl Predicate for StartsWithPredicate { fn eval(&self, variable: &str) -> bool { variable.starts_with(&self.pattern) } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for StartsWithPredicate {} @@ -100,6 +109,10 @@ impl Predicate for EndsWithPredicate { fn eval(&self, variable: &str) -> bool { variable.ends_with(&self.pattern) } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for EndsWithPredicate {} @@ -162,6 +175,10 @@ impl Predicate for ContainsPredicate { fn eval(&self, variable: &str) -> bool { variable.contains(&self.pattern) } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for ContainsPredicate {} diff --git a/src/str/regex.rs b/src/str/regex.rs index 12ab8cc..91cef62 100644 --- a/src/str/regex.rs +++ b/src/str/regex.rs @@ -10,6 +10,7 @@ use std::fmt; use regex; +use core; use reflection; use Predicate; @@ -45,6 +46,10 @@ impl Predicate for RegexPredicate { fn eval(&self, variable: &str) -> bool { self.re.is_match(variable) } + + fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option> { + core::default_find_case(self, expected, variable) + } } impl reflection::PredicateReflection for RegexPredicate {} From 67085fc9d7b4b7703ec12f7e6a12db2bb789ca74 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 21 Jul 2018 15:45:50 -0600 Subject: [PATCH 7/7] feat: Tree view of Predicate result (Case) Finnaly, Fixes #7. Inspired by the work in #39. Created softprops/treeline#3 for trying to find ways to make this more efficient. --- Cargo.toml | 4 ++- examples/case_tree.rs | 15 +++++++++++ src/lib.rs | 4 +++ src/tree.rs | 60 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 examples/case_tree.rs create mode 100644 src/tree.rs diff --git a/Cargo.toml b/Cargo.toml index 7865d7f..72a2f96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,9 @@ difference = { version = "2.0", optional = true } normalize-line-endings = { version = "0.2.2", optional = true } regex = { version="1.0", optional = true } float-cmp = { version="0.4", optional = true } +treeline = { version = "0.1", optional = true } [features] -default = ["difference", "regex", "float-cmp", "normalize-line-endings"] +default = ["difference", "regex", "float-cmp", "normalize-line-endings", "tree"] unstable = [] +tree = ["treeline",] diff --git a/examples/case_tree.rs b/examples/case_tree.rs new file mode 100644 index 0000000..bcc6a6f --- /dev/null +++ b/examples/case_tree.rs @@ -0,0 +1,15 @@ +extern crate predicates; + +use predicates::prelude::*; +use predicates::tree::CaseTreeExt; + +fn main() { + let pred = predicate::ne(5).not().and(predicate::ge(5)); + + let var = 5; + let case = pred.find_case(true, &var); + if let Some(case) = case { + println!("var is {}", var); + println!("{}", case.tree()); + } +} diff --git a/src/lib.rs b/src/lib.rs index a98561b..a29e03b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,8 @@ extern crate float_cmp; extern crate normalize_line_endings; #[cfg(feature = "regex")] extern crate regex; +#[cfg(feature = "treeline")] +extern crate treeline; pub mod prelude; @@ -123,3 +125,5 @@ pub mod boolean; pub mod float; pub mod path; pub mod str; +#[cfg(feature = "tree")] +pub mod tree; diff --git a/src/tree.rs b/src/tree.rs new file mode 100644 index 0000000..5445a77 --- /dev/null +++ b/src/tree.rs @@ -0,0 +1,60 @@ +// Copyright (c) 2018 The predicates-rs Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Render `Case` as a tree. + +use std::fmt; + +use treeline; + +use reflection; + +/// Render `Self` as a displayable tree. +pub trait CaseTreeExt { + /// Render `Self` as a displayable tree. + fn tree(&self) -> CaseTree; +} + +impl<'a> CaseTreeExt for reflection::Case<'a> { + fn tree(&self) -> CaseTree { + CaseTree(convert(self)) + } +} + +type CaseTreeInner = treeline::Tree>; + +fn convert<'a>(case: &reflection::Case<'a>) -> CaseTreeInner { + let mut leaves: Vec = vec![]; + + leaves.extend(case.predicate().iter().flat_map(|pred| { + pred.parameters().map(|item| { + let root: Box = Box::new(item.to_string()); + treeline::Tree::new(root, vec![]) + }) + })); + + leaves.extend(case.products().map(|item| { + let root: Box = Box::new(item.to_string()); + treeline::Tree::new(root, vec![]) + })); + + leaves.extend(case.children().map(|item| convert(item))); + + let root = Box::new(case.predicate().map(|p| p.to_string()).unwrap_or_default()); + CaseTreeInner::new(root, leaves) +} + +/// A `Case` rendered as a tree for display. +#[allow(missing_debug_implementations)] +pub struct CaseTree(CaseTreeInner); + +impl fmt::Display for CaseTree { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +}