use itertools::Itertools;
pub struct WordIterator<'a> {
string: &'a str,
index: usize,
}
impl<'a> WordIterator<'a> {
pub fn new(string: &str) -> WordIterator<'_> {
WordIterator { string, index: 0 }
}
}
fn char_at(str: &str, index: usize) -> char {
if index >= str.len() {
panic!("char_at: index out of bounds");
}
str[index..=index].chars().next().unwrap()
}
impl<'a> Iterator for WordIterator<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
while self.index < self.string.len() && &self.string[self.index..=self.index] == "_" {
self.index += 1;
}
if self.index >= self.string.len() {
return None;
}
let mut i = self.index + 1;
let current_word_is_number = i < self.string.len() && char_at(self.string, i).is_digit(10);
while i < self.string.len() {
let current = char_at(self.string, i);
if current == '_' || current.is_uppercase() {
break;
}
if !current_word_is_number && current.is_digit(10) {
break;
}
i += 1;
}
let result = &self.string[self.index..i];
self.index = i;
Some(result)
}
}
pub trait CaseOperations {
fn to_class_case(self) -> String;
fn to_snake_case(self) -> String;
fn to_upper_case_words(self) -> String;
}
fn iterator_to_class_case<S: AsRef<str>, T: Iterator<Item = S>>(it: T) -> String {
it.map(|x| {
if char_at(x.as_ref(), 0).is_digit(10) {
x.as_ref().to_uppercase()
} else {
format!(
"{}{}",
x.as_ref()[0..1].to_uppercase(),
x.as_ref()[1..].to_lowercase()
)
}
})
.join("")
}
pub fn ends_with_digit<S: AsRef<str>>(s: S) -> bool {
let str = s.as_ref();
if str.is_empty() {
false
} else {
str[str.len() - 1..str.len()]
.chars()
.next()
.unwrap()
.is_digit(10)
}
}
fn iterator_to_snake_case<S: AsRef<str>, T: Iterator<Item = S>>(it: T) -> String {
let mut parts = it.map(|x| x.as_ref().to_lowercase()).collect_vec();
replace_all_sub_vecs(&mut parts, &["na", "n"]);
replace_all_sub_vecs(&mut parts, &["open", "g", "l"]);
replace_all_sub_vecs(&mut parts, &["i", "o"]);
replace_all_sub_vecs(&mut parts, &["2", "d"]);
replace_all_sub_vecs(&mut parts, &["3", "d"]);
replace_all_sub_vecs(&mut parts, &["4", "d"]);
let mut string = String::new();
for (i, part) in parts.into_iter().enumerate() {
if part.is_empty() {
continue;
}
let all_digits = part.chars().all(|c| c.is_digit(10));
if i > 0 && (!all_digits || ends_with_digit(&string)) {
string.push('_');
}
string.push_str(&part);
}
string
}
fn iterator_to_upper_case_words<S: AsRef<str>, T: Iterator<Item = S>>(it: T) -> String {
it.map(|x| x.as_ref().to_uppercase()).join("_")
}
fn replace_all_sub_vecs(parts: &mut Vec<String>, needle: &[&str]) {
let mut any_found = true;
while any_found {
any_found = false;
if parts.len() + 1 >= needle.len() {
for i in 0..parts.len() + 1 - needle.len() {
if parts[i..i + needle.len()] == needle[..] {
for _ in 0..needle.len() - 1 {
parts.remove(i + 1);
}
parts[i] = needle.join("");
any_found = true;
break;
}
}
}
}
}
impl<'a> CaseOperations for &'a str {
fn to_class_case(self) -> String {
iterator_to_class_case(WordIterator::new(self))
}
fn to_snake_case(self) -> String {
iterator_to_snake_case(WordIterator::new(self))
}
fn to_upper_case_words(self) -> String {
iterator_to_upper_case_words(WordIterator::new(self))
}
}
impl<'a> CaseOperations for Vec<&'a str> {
fn to_class_case(self) -> String {
iterator_to_class_case(self.into_iter())
}
fn to_snake_case(self) -> String {
iterator_to_snake_case(self.into_iter())
}
fn to_upper_case_words(self) -> String {
iterator_to_upper_case_words(self.into_iter())
}
}
pub fn trim_slice<T, F>(slice: &[T], mut f: F) -> &[T]
where
F: FnMut(&T) -> bool,
{
let first_good_index = if let Some(index) = slice.iter().position(|item| !f(item)) {
index
} else {
return &[];
};
let last_good_index = slice
.iter()
.rposition(|item| !f(item))
.expect("slice must contain good items as checked above");
&slice[first_good_index..=last_good_index]
}
#[test]
fn test_trim_slice1() {
assert_eq!(
trim_slice(&["", "asd", "dsa", "", ""], |x| x.is_empty()),
&["asd", "dsa"]
);
}