use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt;
use crate::key::Key;
use crate::ranking::Ranking;
type BaseSortFn<T> = Box<dyn Fn(&RankedItem<T>, &RankedItem<T>) -> Ordering>;
type SorterFn<T> = Box<dyn Fn(Vec<RankedItem<T>>) -> Vec<RankedItem<T>>>;
#[derive(Debug, Clone, PartialEq)]
pub struct RankedItem<'a, T> {
pub item: &'a T,
pub index: usize,
pub rank: Ranking,
pub ranked_value: Cow<'a, str>,
pub key_index: usize,
pub key_threshold: Option<Ranking>,
}
pub struct MatchSorterOptions<T> {
pub keys: Vec<Key<T>>,
pub threshold: Ranking,
pub keep_diacritics: bool,
pub base_sort: Option<BaseSortFn<T>>,
pub sorter: Option<SorterFn<T>>,
}
impl<T> Default for MatchSorterOptions<T> {
fn default() -> Self {
Self {
keys: Vec::new(),
threshold: Ranking::Matches(1.0),
keep_diacritics: false,
base_sort: None,
sorter: None,
}
}
}
impl<T> fmt::Debug for MatchSorterOptions<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MatchSorterOptions")
.field("keys", &format_args!("[{} key(s)]", self.keys.len()))
.field("threshold", &self.threshold)
.field("keep_diacritics", &self.keep_diacritics)
.field(
"base_sort",
if self.base_sort.is_some() {
&"Some(<fn>)" as &dyn fmt::Debug
} else {
&"None" as &dyn fmt::Debug
},
)
.field(
"sorter",
if self.sorter.is_some() {
&"Some(<fn>)" as &dyn fmt::Debug
} else {
&"None" as &dyn fmt::Debug
},
)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_keep_diacritics_is_false() {
let opts = MatchSorterOptions::<String>::default();
assert!(!opts.keep_diacritics);
}
#[test]
fn default_threshold_is_matches() {
let opts = MatchSorterOptions::<String>::default();
assert_eq!(opts.threshold, Ranking::Matches(1.0));
}
#[test]
fn default_keys_is_empty() {
let opts = MatchSorterOptions::<String>::default();
assert!(opts.keys.is_empty());
}
#[test]
fn default_base_sort_is_none() {
let opts = MatchSorterOptions::<String>::default();
assert!(opts.base_sort.is_none());
}
#[test]
fn default_sorter_is_none() {
let opts = MatchSorterOptions::<String>::default();
assert!(opts.sorter.is_none());
}
#[test]
fn debug_formatting() {
let opts = MatchSorterOptions::<String>::default();
let debug_str = format!("{opts:?}");
assert!(debug_str.contains("keep_diacritics"));
assert!(debug_str.contains("threshold"));
assert!(debug_str.contains("MatchSorterOptions"));
}
#[test]
fn debug_formatting_with_base_sort() {
let opts = MatchSorterOptions::<String> {
base_sort: Some(Box::new(|_a, _b| Ordering::Equal)),
..Default::default()
};
let debug_str = format!("{opts:?}");
assert!(debug_str.contains("Some(<fn>)"));
}
#[test]
fn ranked_item_construction() {
let item = "hello".to_owned();
let ranked = RankedItem {
item: &item,
index: 0,
rank: Ranking::CaseSensitiveEqual,
ranked_value: Cow::Borrowed("hello"),
key_index: 0,
key_threshold: None,
};
assert_eq!(ranked.rank, Ranking::CaseSensitiveEqual);
assert_eq!(ranked.ranked_value, "hello");
assert_eq!(ranked.index, 0);
assert_eq!(ranked.key_index, 0);
assert_eq!(ranked.key_threshold, None);
assert_eq!(*ranked.item, "hello");
}
#[test]
fn ranked_item_with_threshold() {
let item = 42u32;
let ranked = RankedItem {
item: &item,
index: 3,
rank: Ranking::Contains,
ranked_value: Cow::Borrowed("forty-two"),
key_index: 1,
key_threshold: Some(Ranking::StartsWith),
};
assert_eq!(ranked.key_threshold, Some(Ranking::StartsWith));
assert_eq!(*ranked.item, 42);
}
#[test]
fn ranked_item_debug() {
let item = "test".to_owned();
let ranked = RankedItem {
item: &item,
index: 0,
rank: Ranking::Acronym,
ranked_value: Cow::Borrowed("test"),
key_index: 0,
key_threshold: None,
};
let debug_str = format!("{ranked:?}");
assert!(debug_str.contains("Acronym"));
assert!(debug_str.contains("test"));
}
#[test]
fn ranked_item_clone() {
let item = "world".to_owned();
let ranked = RankedItem {
item: &item,
index: 1,
rank: Ranking::StartsWith,
ranked_value: Cow::Borrowed("world"),
key_index: 2,
key_threshold: Some(Ranking::Contains),
};
let cloned = ranked.clone();
assert_eq!(ranked, cloned);
}
#[test]
fn ranked_item_partial_eq() {
let item = "a".to_owned();
let a = RankedItem {
item: &item,
index: 0,
rank: Ranking::Equal,
ranked_value: Cow::Borrowed("a"),
key_index: 0,
key_threshold: None,
};
let b = RankedItem {
item: &item,
index: 0,
rank: Ranking::Equal,
ranked_value: Cow::Borrowed("a"),
key_index: 0,
key_threshold: None,
};
assert_eq!(a, b);
}
#[test]
fn ranked_item_partial_eq_different_rank() {
let item = "a".to_owned();
let a = RankedItem {
item: &item,
index: 0,
rank: Ranking::Equal,
ranked_value: Cow::Borrowed("a"),
key_index: 0,
key_threshold: None,
};
let b = RankedItem {
item: &item,
index: 0,
rank: Ranking::Contains,
ranked_value: Cow::Borrowed("a"),
key_index: 0,
key_threshold: None,
};
assert_ne!(a, b);
}
}