predicates/str/
difference.rs

1// Copyright (c) 2018 The predicates-rs Project Developers.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::borrow;
10use std::fmt;
11
12use crate::reflection;
13use crate::Predicate;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16enum DistanceOp {
17    Similar,
18    Different,
19}
20
21impl DistanceOp {
22    fn eval(self, limit: i32, distance: i32) -> bool {
23        match self {
24            DistanceOp::Similar => distance <= limit,
25            DistanceOp::Different => limit < distance,
26        }
27    }
28}
29
30/// Predicate that diffs two strings.
31///
32/// This is created by the `predicate::str::similar`.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct DifferencePredicate {
35    orig: borrow::Cow<'static, str>,
36    split: borrow::Cow<'static, str>,
37    distance: i32,
38    op: DistanceOp,
39}
40
41impl DifferencePredicate {
42    /// The split used when identifying changes.
43    ///
44    /// Common splits include:
45    /// - `""` for char-level.
46    /// - `" "` for word-level.
47    /// - `"\n"` for line-level.
48    ///
49    /// Default: `"\n"`
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// use predicates::prelude::*;
55    ///
56    /// let predicate_fn = predicate::str::similar("Hello World").split(" ");
57    /// assert_eq!(true, predicate_fn.eval("Hello World"));
58    /// ```
59    pub fn split<S>(mut self, split: S) -> Self
60    where
61        S: Into<borrow::Cow<'static, str>>,
62    {
63        self.split = split.into();
64        self
65    }
66
67    /// The maximum allowed edit distance.
68    ///
69    /// Default: `0`
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use predicates::prelude::*;
75    ///
76    /// let predicate_fn = predicate::str::similar("Hello World!").split("").distance(1);
77    /// assert_eq!(true, predicate_fn.eval("Hello World!"));
78    /// assert_eq!(true, predicate_fn.eval("Hello World"));
79    /// assert_eq!(false, predicate_fn.eval("Hello World?"));
80    /// ```
81    pub fn distance(mut self, distance: i32) -> Self {
82        self.distance = distance;
83        self
84    }
85}
86
87impl Predicate<str> for DifferencePredicate {
88    fn eval(&self, edit: &str) -> bool {
89        let change = difference::Changeset::new(&self.orig, edit, &self.split);
90        self.op.eval(self.distance, change.distance)
91    }
92
93    fn find_case<'a>(&'a self, expected: bool, variable: &str) -> Option<reflection::Case<'a>> {
94        let change = difference::Changeset::new(&self.orig, variable, &self.split);
95        let result = self.op.eval(self.distance, change.distance);
96        if result == expected {
97            Some(
98                reflection::Case::new(Some(self), result)
99                    .add_product(reflection::Product::new("actual distance", change.distance))
100                    .add_product(reflection::Product::new("diff", change)),
101            )
102        } else {
103            None
104        }
105    }
106}
107
108impl reflection::PredicateReflection for DifferencePredicate {
109    fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + 'a> {
110        let params = vec![reflection::Parameter::new("original", &self.orig)];
111        Box::new(params.into_iter())
112    }
113}
114
115impl fmt::Display for DifferencePredicate {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        match self.op {
118            DistanceOp::Similar => write!(f, "var - original <= {}", self.distance),
119            DistanceOp::Different => write!(f, "{} < var - original", self.distance),
120        }
121    }
122}
123
124/// Creates a new `Predicate` that diffs two strings.
125///
126/// # Examples
127///
128/// ```
129/// use predicates::prelude::*;
130///
131/// let predicate_fn = predicate::str::diff("Hello World");
132/// assert_eq!(false, predicate_fn.eval("Hello World"));
133/// assert_eq!(true, predicate_fn.eval("Goodbye World"));
134/// ```
135pub fn diff<S>(orig: S) -> DifferencePredicate
136where
137    S: Into<borrow::Cow<'static, str>>,
138{
139    DifferencePredicate {
140        orig: orig.into(),
141        split: "\n".into(),
142        distance: 0,
143        op: DistanceOp::Different,
144    }
145}
146
147/// Creates a new `Predicate` that checks strings for how similar they are.
148///
149/// # Examples
150///
151/// ```
152/// use predicates::prelude::*;
153///
154/// let predicate_fn = predicate::str::similar("Hello World");
155/// assert_eq!(true, predicate_fn.eval("Hello World"));
156/// assert_eq!(false, predicate_fn.eval("Goodbye World"));
157/// ```
158pub fn similar<S>(orig: S) -> DifferencePredicate
159where
160    S: Into<borrow::Cow<'static, str>>,
161{
162    DifferencePredicate {
163        orig: orig.into(),
164        split: "\n".into(),
165        distance: 0,
166        op: DistanceOp::Similar,
167    }
168}