1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum CaseFormat {
6 CamelCase,
8 PascalCase,
10 SnakeCase,
12 ScreamingSnakeCase,
14 KebabCase,
16 ScreamingKebabCase,
18}
19
20impl CaseFormat {
21 pub fn pattern(&self) -> &str {
23 match self {
24 CaseFormat::CamelCase => r"\b[a-z]+(?:[A-Z][a-z0-9]*)+\b",
25 CaseFormat::PascalCase => r"\b[A-Z][a-z0-9]+(?:[A-Z][a-z0-9]*)+\b",
26 CaseFormat::SnakeCase => r"\b[a-z]+(?:_[a-z0-9]+)+\b",
27 CaseFormat::ScreamingSnakeCase => r"\b[A-Z]+(?:_[A-Z0-9]+)+\b",
28 CaseFormat::KebabCase => r"\b[a-z]+(?:-[a-z0-9]+)+\b",
29 CaseFormat::ScreamingKebabCase => r"\b[A-Z]+(?:-[A-Z0-9]+)+\b",
30 }
31 }
32
33 pub fn split_words(&self, text: &str) -> Vec<String> {
35 match self {
36 CaseFormat::CamelCase | CaseFormat::PascalCase => {
37 let mut words = Vec::new();
39 let mut current_word = String::new();
40
41 for ch in text.chars() {
42 if ch.is_uppercase() && !current_word.is_empty() {
43 words.push(current_word.to_lowercase());
44 current_word = String::new();
45 }
46 current_word.push(ch);
47 }
48
49 if !current_word.is_empty() {
50 words.push(current_word.to_lowercase());
51 }
52
53 words
54 }
55 CaseFormat::SnakeCase | CaseFormat::ScreamingSnakeCase => {
56 text.split('_')
58 .filter(|s| !s.is_empty())
59 .map(|s| s.to_lowercase())
60 .collect()
61 }
62 CaseFormat::KebabCase | CaseFormat::ScreamingKebabCase => {
63 text.split('-')
65 .filter(|s| !s.is_empty())
66 .map(|s| s.to_lowercase())
67 .collect()
68 }
69 }
70 }
71
72 pub fn join_words(&self, words: &[String], prefix: &str, suffix: &str) -> String {
74 if words.is_empty() {
75 return String::new();
76 }
77
78 let result = match self {
79 CaseFormat::CamelCase => {
80 let first = words[0].to_lowercase();
81 let rest: String = words[1..]
82 .iter()
83 .map(|w| {
84 let mut chars = w.chars();
85 match chars.next() {
86 None => String::new(),
87 Some(first) => first.to_uppercase().chain(chars).collect(),
88 }
89 })
90 .collect();
91 format!("{}{}", first, rest)
92 }
93 CaseFormat::PascalCase => words
94 .iter()
95 .map(|w| {
96 let mut chars = w.chars();
97 match chars.next() {
98 None => String::new(),
99 Some(first) => first.to_uppercase().chain(chars).collect(),
100 }
101 })
102 .collect::<String>(),
103 CaseFormat::SnakeCase => words
104 .iter()
105 .map(|w| w.to_lowercase())
106 .collect::<Vec<_>>()
107 .join("_"),
108 CaseFormat::ScreamingSnakeCase => words
109 .iter()
110 .map(|w| w.to_uppercase())
111 .collect::<Vec<_>>()
112 .join("_"),
113 CaseFormat::KebabCase => words
114 .iter()
115 .map(|w| w.to_lowercase())
116 .collect::<Vec<_>>()
117 .join("-"),
118 CaseFormat::ScreamingKebabCase => words
119 .iter()
120 .map(|w| w.to_uppercase())
121 .collect::<Vec<_>>()
122 .join("-"),
123 };
124
125 format!("{}{}{}", prefix, result, suffix)
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_camel_split() {
135 let words = CaseFormat::CamelCase.split_words("firstName");
136 assert_eq!(words, vec!["first", "name"]);
137 }
138
139 #[test]
140 fn test_snake_split() {
141 let words = CaseFormat::SnakeCase.split_words("first_name");
142 assert_eq!(words, vec!["first", "name"]);
143 }
144
145 #[test]
146 fn test_camel_join() {
147 let words = vec!["first".to_string(), "name".to_string()];
148 assert_eq!(
149 CaseFormat::CamelCase.join_words(&words, "", ""),
150 "firstName"
151 );
152 }
153
154 #[test]
155 fn test_snake_join() {
156 let words = vec!["first".to_string(), "name".to_string()];
157 assert_eq!(
158 CaseFormat::SnakeCase.join_words(&words, "", ""),
159 "first_name"
160 );
161 }
162
163 #[test]
164 fn test_with_prefix_suffix() {
165 let words = vec!["first".to_string(), "name".to_string()];
166 assert_eq!(
167 CaseFormat::SnakeCase.join_words(&words, "old_", "_v1"),
168 "old_first_name_v1"
169 );
170 }
171}