use convert_case::{Boundary, Case, Casing, Pattern};
#[cfg(feature = "random")]
use rand::prelude::*;
pub fn is_case<T: AsRef<str>>(s: T, case: Case) -> bool {
s.as_ref() == s.as_ref().to_case(case)
}
#[derive(Debug, Clone)]
pub struct CaseDetector {
cases: Vec<Case<'static>>,
}
impl CaseDetector {
pub fn new() -> Self {
Self { cases: Vec::new() }
}
pub fn add_case(mut self, case: Case<'static>) -> Self {
self.cases.push(case);
self
}
pub fn add_cases(mut self, cases: &[Case<'static>]) -> Self {
self.cases.extend(cases.iter().copied());
self
}
pub fn remove_case(mut self, case: Case<'static>) -> Self {
self.cases.retain(|&c| c != case);
self
}
pub fn remove_cases(mut self, cases: &[Case<'static>]) -> Self {
for case in cases {
self.cases.retain(|&c| c != *case);
}
self
}
pub fn detect_cases<T: AsRef<str>>(&self, s: T) -> Vec<Case<'static>> {
let s = s.as_ref();
self.cases
.iter()
.filter(|&&case| is_case(s, case))
.copied()
.collect()
}
}
impl Default for CaseDetector {
fn default() -> Self {
Self {
cases: Case::all_cases().to_vec(),
}
}
}
pub mod pattern {
use super::*;
pub const TOGGLE: Pattern = Pattern::Custom(|words| {
words
.iter()
.map(|word| {
let mut chars = word.chars();
if let Some(c) = chars.next() {
[c.to_lowercase().collect(), chars.as_str().to_uppercase()].concat()
} else {
String::new()
}
})
.collect()
});
pub const ALTERNATING: Pattern = Pattern::Custom(|words| {
let mut upper = false;
words
.iter()
.map(|word| {
word.chars()
.map(|letter| {
if letter.is_uppercase() || letter.is_lowercase() {
if upper {
upper = false;
letter.to_uppercase().to_string()
} else {
upper = true;
letter.to_lowercase().to_string()
}
} else {
letter.to_string()
}
})
.collect()
})
.collect()
});
#[cfg(feature = "random")]
pub const RANDOM: Pattern = Pattern::Custom(|words| {
let mut rng = rand::thread_rng();
words
.iter()
.map(|word| {
word.chars()
.map(|letter| {
if rng.gen::<f32>() > 0.5 {
letter.to_uppercase().to_string()
} else {
letter.to_lowercase().to_string()
}
})
.collect()
})
.collect()
});
#[cfg(feature = "random")]
pub const PSEUDO_RANDOM: Pattern = Pattern::Custom(|words| {
let mut rng = rand::thread_rng();
let mut alt: Option<bool> = None;
words
.iter()
.map(|word| {
word.chars()
.map(|letter| {
match alt {
None => {
if rng.gen::<f32>() > 0.5 {
alt = Some(false); letter.to_uppercase().to_string()
} else {
alt = Some(true); letter.to_lowercase().to_string()
}
}
Some(upper) => {
alt = None;
if upper {
letter.to_uppercase().to_string()
} else {
letter.to_lowercase().to_string()
}
}
}
})
.collect()
})
.collect()
});
}
pub mod case {
use super::*;
pub const TOGGLE: Case = Case::Custom {
boundaries: &[Boundary::Space],
pattern: pattern::TOGGLE,
delimiter: " ",
};
pub const ALTERNATING: Case = Case::Custom {
boundaries: &[Boundary::Space],
pattern: pattern::ALTERNATING,
delimiter: " ",
};
#[cfg(any(doc, feature = "random"))]
#[cfg(feature = "random")]
pub const RANDOM: Case = Case::Custom {
boundaries: &[Boundary::Space],
pattern: pattern::RANDOM,
delimiter: " ",
};
#[cfg(any(doc, feature = "random"))]
#[cfg(feature = "random")]
pub const PSEUDO_RANDOM: Case = Case::Custom {
boundaries: &[Boundary::Space],
pattern: pattern::PSEUDO_RANDOM,
delimiter: " ",
};
}
#[cfg(test)]
mod test {
use super::*;
use convert_case::Casing;
#[test]
fn toggle_case() {
assert_eq!("test_toggle".to_case(case::TOGGLE), "tEST tOGGLE");
}
#[cfg(feature = "random")]
#[test]
fn pseudo_no_triples() {
let words = vec!["abcdefg", "hijklmnop", "qrstuv", "wxyz"];
for _ in 0..5 {
let new = pattern::PSEUDO_RANDOM.mutate(&words).join("");
let mut iter = new
.chars()
.zip(new.chars().skip(1))
.zip(new.chars().skip(2));
assert!(!iter
.clone()
.any(|((a, b), c)| a.is_lowercase() && b.is_lowercase() && c.is_lowercase()));
assert!(
!iter.any(|((a, b), c)| a.is_uppercase() && b.is_uppercase() && c.is_uppercase())
);
}
}
#[cfg(feature = "random")]
#[test]
fn randoms_are_random() {
let words = vec!["abcdefg", "hijklmnop", "qrstuv", "wxyz"];
for _ in 0..5 {
let transformed = pattern::PSEUDO_RANDOM.mutate(&words);
assert_ne!(words, transformed);
let transformed = pattern::RANDOM.mutate(&words);
assert_ne!(words, transformed);
}
}
}