#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Case {
Snake,
Upper,
Pascal,
NonConformist,
}
pub(super) fn classify(name: &str) -> Case {
if name.is_empty() {
return Case::NonConformist;
}
if name.starts_with('_') || name.ends_with('_') || name.contains("__") {
return Case::NonConformist;
}
if !name
.chars()
.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
{
return Case::NonConformist;
}
let has_lower = name.chars().any(|ch| ch.is_ascii_lowercase());
let has_upper = name.chars().any(|ch| ch.is_ascii_uppercase());
if !has_upper {
return Case::Snake;
}
if !has_lower {
return Case::Upper;
}
if !name.contains('_') && name.starts_with(|ch: char| ch.is_ascii_uppercase()) {
return Case::Pascal;
}
Case::NonConformist
}
pub(super) fn word_count(name: &str, case: Case) -> usize {
match case {
Case::Snake | Case::Upper => name
.split('_')
.filter(|segment| !segment.is_empty())
.count()
.max(1),
Case::Pascal => pascal_word_count(name),
Case::NonConformist => 1,
}
}
fn pascal_word_count(name: &str) -> usize {
let chars: Vec<char> = name.chars().collect();
let mut count = 0;
for (index, &ch) in chars.iter().enumerate() {
let starts_word = if index == 0 {
true
} else if ch.is_ascii_uppercase() {
let previous = chars[index - 1];
let next_is_lower = chars.get(index + 1).is_some_and(char::is_ascii_lowercase);
!previous.is_ascii_uppercase() || next_is_lower
} else {
false
};
if starts_word {
count += 1;
}
}
count.max(1)
}
#[cfg(test)]
mod tests;