use std::ops::Deref;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ModifierSet<S>(
)`, since build.rs outputs
pub(crate) S,
);
impl<S: Deref<Target = str>> ModifierSet<S> {
pub fn from_raw_dotted(s: S) -> Self {
debug_assert!(
!s.contains(".."),
"ModifierSet::from_raw_dotted called with string containing empty modifier"
);
Self(s)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn as_deref(&self) -> ModifierSet<&str> {
ModifierSet(&self.0)
}
pub fn insert_raw(&mut self, m: &str)
where
S: for<'a> std::ops::AddAssign<&'a str>,
{
if !self.0.is_empty() {
self.0 += ".";
}
self.0 += m;
}
pub fn iter(&self) -> impl Iterator<Item = &str> {
self.into_iter()
}
pub fn contains(&self, m: &str) -> bool {
self.iter().any(|lhs| lhs == m)
}
pub fn best_match_in<'a, T>(
&self,
variants: impl Iterator<Item = (ModifierSet<&'a str>, T)>,
) -> Option<T> {
let mut best = None;
let mut best_score = None;
for candidate in variants.filter(|(set, _)| self.is_subset(*set)) {
let mut matching = 0;
let mut total = 0;
for modifier in candidate.0.iter() {
if self.contains(modifier) {
matching += 1;
}
total += 1;
}
let score = (matching, std::cmp::Reverse(total));
if best_score.is_none_or(|b| score > b) {
best = Some(candidate.1);
best_score = Some(score);
}
}
best
}
pub fn is_subset(&self, other: ModifierSet<&str>) -> bool {
self.iter().all(|m| other.contains(m))
}
}
impl<S: Default> Default for ModifierSet<S> {
fn default() -> Self {
Self(S::default())
}
}
impl<'a, S: Deref<Target = str>> IntoIterator for &'a ModifierSet<S> {
type Item = &'a str;
type IntoIter = std::str::Split<'a, char>;
fn into_iter(self) -> Self::IntoIter {
let mut iter = self.0.split('.');
if self.0.is_empty() {
let _ = iter.next();
}
iter
}
}
impl<'a> IntoIterator for ModifierSet<&'a str> {
type Item = &'a str;
type IntoIter = std::str::Split<'a, char>;
fn into_iter(self) -> Self::IntoIter {
let mut iter = self.0.split('.');
if self.0.is_empty() {
let _ = iter.next();
}
iter
}
}
#[cfg(test)]
mod tests {
type ModifierSet = super::ModifierSet<&'static str>;
#[test]
fn default_is_empty() {
assert!(ModifierSet::default().is_empty());
}
#[test]
fn iter_count() {
assert_eq!(ModifierSet::default().iter().count(), 0);
assert_eq!(ModifierSet::from_raw_dotted("a").iter().count(), 1);
assert_eq!(ModifierSet::from_raw_dotted("a.b").iter().count(), 2);
assert_eq!(ModifierSet::from_raw_dotted("a.b.c").iter().count(), 3);
}
#[test]
fn subset() {
assert!(ModifierSet::from_raw_dotted("a")
.is_subset(ModifierSet::from_raw_dotted("a.b")));
assert!(ModifierSet::from_raw_dotted("a")
.is_subset(ModifierSet::from_raw_dotted("b.a")));
assert!(ModifierSet::from_raw_dotted("a.b")
.is_subset(ModifierSet::from_raw_dotted("b.c.a")));
}
#[test]
fn best_match() {
assert_eq!(
ModifierSet::from_raw_dotted("a.b").best_match_in(
[
(ModifierSet::from_raw_dotted("a.c"), 1),
(ModifierSet::from_raw_dotted("a.b"), 2),
]
.into_iter()
),
Some(2)
);
assert_eq!(
ModifierSet::from_raw_dotted("a").best_match_in(
[
(ModifierSet::from_raw_dotted("a"), 1),
(ModifierSet::from_raw_dotted("a.b"), 2),
]
.into_iter()
),
Some(1)
);
assert_eq!(
ModifierSet::from_raw_dotted("a.b").best_match_in(
[
(ModifierSet::from_raw_dotted("a"), 1),
(ModifierSet::from_raw_dotted("a.b"), 2),
]
.into_iter()
),
Some(2)
);
assert_eq!(
ModifierSet::default().best_match_in(
[
(ModifierSet::from_raw_dotted("a"), 1),
(ModifierSet::from_raw_dotted("b"), 2)
]
.into_iter()
),
Some(1)
);
}
}