1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use std::collections::HashSet;

use regex::Regex;
use ruby_inflector::case::{
    to_case_camel_like, to_class_case as to_class_case_original, CamelOptions,
};

// See https://github.com/whatisinternet/Inflector/pull/87
// Note that as of the PR that adds this comment, we are now using https://github.com/alexevanczuk/ruby_inflector,
// so that we have an easier time making this inflector more specific to ruby applications (for now)
pub fn to_class_case(
    s: &str,
    should_singularize: bool,
    acronyms: &HashSet<String>,
) -> String {
    let options = CamelOptions {
        new_word: true,
        last_char: ' ',
        first_word: false,
        injectable_char: ' ',
        has_seperator: false,
        inverted: false,
    };

    let mut class_name = if should_singularize {
        to_class_case_original(s, acronyms)
    } else {
        to_case_camel_like(s, options, acronyms)
    };

    // let mut class_name = s.to_class_case();
    if class_name.contains("Statu") {
        let re = Regex::new("Statuse$").unwrap();
        class_name = re.replace_all(&class_name, "Status").to_string();
        let re = Regex::new("Statu$").unwrap();

        class_name = re.replace_all(&class_name, "Status").to_string();

        let re = Regex::new("Statuss").unwrap();
        re.replace_all(&class_name, "Status").to_string();
    }

    if class_name.contains("Daum") {
        let re = Regex::new("Daum").unwrap();
        class_name = re.replace_all(&class_name, "Datum").to_string();
    }

    if class_name.contains("Lefe") {
        let re = Regex::new("Lefe").unwrap();
        class_name = re.replace_all(&class_name, "Leave").to_string();
    }

    if class_name.contains("Leafe") {
        let re = Regex::new("Leafe").unwrap();
        class_name = re.replace_all(&class_name, "Leave").to_string();
    }

    class_name
}

pub fn camelize(s: &str, acronyms: &HashSet<String>) -> String {
    // Meant to emulate https://github.com/rails/rails/blob/e88857bbb9d4e1dd64555c34541301870de4a45b/activesupport/lib/active_support/inflector/methods.rb#L69
    //
    // def camelize(term, uppercase_first_letter = true)
    //   string = term.to_s
    //   # String#camelize takes a symbol (:upper or :lower), so here we also support :lower to keep the methods consistent.
    //   if !uppercase_first_letter || uppercase_first_letter == :lower
    //     string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match }
    //   else
    //     string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match }
    //   end
    //   string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
    //     word = $2
    //     substituted = inflections.acronyms[word] || word.capitalize! || word
    //     $1 ? "::#{substituted}" : substituted
    //   end
    //   string
    // end

    let lowercase_acronyms = acronyms
        .iter()
        .map(|acronym| acronym.to_lowercase())
        .collect::<HashSet<String>>();

    let mut new_string = s.to_string();
    // Replace the beginning of the word, matched with lowercase letters, with either a matching inflection or a capitalized version of the word
    let re = Regex::new("^[a-z\\d]*").unwrap();
    new_string = re
        .replace(&new_string, |caps: &regex::Captures| {
            let word = caps.get(0).unwrap().as_str();
            if lowercase_acronyms.contains(word) {
                word.to_uppercase()
            } else {
                capitalize(word)
            }
        })
        .to_mut()
        .to_string();

    let re = Regex::new("(?:_|(/))([a-z\\d]*)").unwrap();

    new_string = re
        .replace_all(&new_string, |caps: &regex::Captures| {
            let matched_slash = caps.get(1);
            let word = caps.get(2).unwrap().as_str();
            let capitalized_word = if lowercase_acronyms.contains(word) {
                word.to_uppercase()
            } else {
                capitalize(word)
            };

            if matched_slash.is_some() {
                format!("::{}", capitalized_word)
            } else {
                capitalized_word
            }
        })
        .to_mut()
        .to_string();

    new_string
}

/// Capitalizes the first character in s.
fn capitalize(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
    }
}

// Add tests
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_trivial() {
        let actual = to_class_case("my_string", false, &HashSet::new());
        let expected = "MyString";
        assert_eq!(expected, actual);
    }

    #[test]
    fn test_digits() {
        let actual =
            to_class_case("my_string_401k_thing", false, &HashSet::new());
        let expected = "MyString401kThing";
        assert_eq!(expected, actual);
    }
}