1pub fn to_pascal(s: &str) -> String {
33 let bytes = s.as_bytes();
34 let mut out = String::with_capacity(s.len());
35 let mut capitalize = true;
36
37 for i in 0..bytes.len() {
38 let c = bytes[i];
39
40 if c == b'_' {
41 if !out.is_empty() {
42 capitalize = true;
43 }
44 } else if c.is_ascii_uppercase() {
45 let prev_lower = i > 0 && bytes[i - 1].is_ascii_lowercase();
46 let next_lower = i + 1 < bytes.len() && bytes[i + 1].is_ascii_lowercase();
47 let prev_upper = i > 0 && bytes[i - 1].is_ascii_uppercase();
48
49 if capitalize || prev_lower || (next_lower && prev_upper) {
50 out.push(c as char);
51 } else {
52 out.push((c + 32) as char);
53 }
54
55 capitalize = false;
56 } else if capitalize {
57 out.push((c - 32) as char);
58 capitalize = false;
59 } else {
60 out.push(c as char);
61 }
62 }
63
64 out
65}
66
67pub fn to_snake(s: &str) -> String {
69 let bytes = s.as_bytes();
70 let mut out = String::with_capacity(s.len() + 4);
71
72 for i in 0..bytes.len() {
73 let c = bytes[i];
74
75 if c.is_ascii_uppercase() {
76 let prev_lower = i > 0 && bytes[i - 1].is_ascii_lowercase();
77 let next_lower = i + 1 < bytes.len() && bytes[i + 1].is_ascii_lowercase();
78 let prev_upper = i > 0 && bytes[i - 1].is_ascii_uppercase();
79
80 if prev_lower || (next_lower && prev_upper) {
81 out.push('_');
82 }
83
84 out.push((c + 32) as char);
85 } else if c == b'_' {
86 if !out.is_empty() && !out.ends_with('_') {
87 out.push('_');
88 }
89 } else {
90 out.push(c as char);
91 }
92 }
93
94 out
95}
96
97pub fn to_camel(s: &str) -> String {
99 let mut pascal = to_pascal(s);
100
101 if let Some(first) = pascal.as_bytes().first()
102 && first.is_ascii_uppercase()
103 {
104 unsafe {
105 pascal.as_bytes_mut()[0] = first + 32;
106 }
107 }
108
109 pascal
110}
111
112pub fn to_screaming(s: &str) -> String {
114 let bytes = s.as_bytes();
115 let mut out = String::with_capacity(s.len() + 4);
116
117 for i in 0..bytes.len() {
118 let c = bytes[i];
119
120 if c.is_ascii_uppercase() {
121 let prev_lower = i > 0 && bytes[i - 1].is_ascii_lowercase();
122 let next_lower = i + 1 < bytes.len() && bytes[i + 1].is_ascii_lowercase();
123 let prev_upper = i > 0 && bytes[i - 1].is_ascii_uppercase();
124
125 if prev_lower || (next_lower && prev_upper) {
126 out.push('_');
127 }
128
129 out.push(c as char);
130 } else if c == b'_' {
131 if !out.is_empty() && !out.ends_with('_') {
132 out.push('_');
133 }
134 } else {
135 out.push((c - 32) as char);
136 }
137 }
138
139 out
140}
141
142pub fn to_kebab(s: &str) -> String {
144 let bytes = s.as_bytes();
145 let mut out = String::with_capacity(s.len() + 4);
146
147 for i in 0..bytes.len() {
148 let c = bytes[i];
149
150 if c.is_ascii_uppercase() {
151 let prev_lower = i > 0 && bytes[i - 1].is_ascii_lowercase();
152 let next_lower = i + 1 < bytes.len() && bytes[i + 1].is_ascii_lowercase();
153 let prev_upper = i > 0 && bytes[i - 1].is_ascii_uppercase();
154
155 if prev_lower || (next_lower && prev_upper) {
156 out.push('-');
157 }
158
159 out.push((c + 32) as char);
160 } else if c == b'_' {
161 if !out.is_empty() && !out.ends_with('-') {
162 out.push('-');
163 }
164 } else {
165 out.push(c as char);
166 }
167 }
168
169 out
170}
171
172#[macro_export]
180macro_rules! pascal {
181 ($ident:expr => ident) => {
182 syn::Ident::new(
183 &$crate::case::to_pascal(&$ident.to_string()),
184 $ident.span(),
185 )
186 };
187 ($ts:expr => token_stream) => {{
188 let __tokens: Vec<proc_macro2::TokenTree> = $ts.clone().into_iter().collect();
189 let mut __out = proc_macro2::TokenStream::new();
190
191 for (i, __tt) in __tokens.iter().enumerate() {
192 match __tt {
193 proc_macro2::TokenTree::Ident(__ident) => {
194 let __is_last_ident = !__tokens[i + 1..]
195 .iter()
196 .any(|t| matches!(t, proc_macro2::TokenTree::Ident(_)));
197
198 if __is_last_ident {
199 quote::ToTokens::to_tokens(
200 &$crate::pascal!(__ident => ident),
201 &mut __out,
202 );
203 } else {
204 quote::ToTokens::to_tokens(__ident, &mut __out);
205 }
206 }
207 __other => {
208 quote::ToTokens::to_tokens(__other, &mut __out);
209 }
210 }
211 }
212
213 __out
214 }};
215 ($s:expr) => {
216 $crate::case::to_pascal($s)
217 };
218}
219
220#[macro_export]
225macro_rules! snake {
226 ($ident:expr => ident) => {
227 syn::Ident::new(&$crate::case::to_snake(&$ident.to_string()), $ident.span())
228 };
229 ($s:expr) => {
230 $crate::case::to_snake($s)
231 };
232}
233
234#[macro_export]
239macro_rules! camel {
240 ($ident:expr => ident) => {
241 syn::Ident::new(&$crate::case::to_camel(&$ident.to_string()), $ident.span())
242 };
243 ($s:expr) => {
244 $crate::case::to_camel($s)
245 };
246}
247
248#[macro_export]
253macro_rules! screaming {
254 ($ident:expr => ident) => {
255 syn::Ident::new(
256 &$crate::case::to_screaming(&$ident.to_string()),
257 $ident.span(),
258 )
259 };
260 ($s:expr) => {
261 $crate::case::to_screaming($s)
262 };
263}
264
265#[macro_export]
269macro_rules! kebab {
270 ($s:expr) => {
271 $crate::case::to_kebab($s)
272 };
273}