Skip to main content

sofie_core/
case.rs

1/// sofie-macros has no dependencies, so it keeps a copy its tests hold to this one.
2#[must_use]
3pub fn snake_case(name: &str) -> String {
4    let characters: Vec<char> = name.chars().collect();
5    let mut snake = String::with_capacity(name.len() + 4);
6    for (index, &character) in characters.iter().enumerate() {
7        if index > 0 && starts_word(&characters, index) {
8            snake.push('_');
9        }
10        snake.extend(character.to_lowercase());
11    }
12    snake
13}
14
15fn starts_word(characters: &[char], index: usize) -> bool {
16    let current = characters[index];
17    if !current.is_uppercase() {
18        return false;
19    }
20    let previous = characters[index - 1];
21    let next_is_lowercase = characters
22        .get(index + 1)
23        .is_some_and(|next| next.is_lowercase());
24    previous.is_lowercase()
25        || previous.is_ascii_digit()
26        || (previous.is_uppercase() && next_is_lowercase)
27}
28
29#[cfg(test)]
30mod tests {
31    use super::snake_case;
32
33    #[test]
34    fn an_acronym_is_one_word() {
35        assert_eq!(snake_case("HTTPStatus"), "http_status");
36        assert_eq!(snake_case("OrderLine"), "order_line");
37        assert_eq!(snake_case("Line2Item"), "line2_item");
38    }
39}