1pub fn to_pascal(s: &str) -> String {
34 let snake = to_snake(s);
35 let mut out = String::new();
36 let mut capitalize = true;
37
38 for c in snake.chars() {
39 if c == '_' {
40 capitalize = true;
41 } else if capitalize {
42 out.extend(c.to_uppercase());
43 capitalize = false;
44 } else {
45 out.push(c);
46 }
47 }
48
49 out
50}
51
52pub fn to_snake(s: &str) -> String {
54 let mut out = String::new();
55 let chars: Vec<char> = s.chars().collect();
56
57 for (i, &c) in chars.iter().enumerate() {
58 if c.is_uppercase() {
59 let prev_lower = i > 0 && chars[i - 1].is_lowercase();
60 let next_lower = i + 1 < chars.len() && chars[i + 1].is_lowercase();
61 let prev_upper = i > 0 && chars[i - 1].is_uppercase();
62
63 if prev_lower || (next_lower && prev_upper) {
64 out.push('_');
65 }
66
67 out.extend(c.to_lowercase());
68 } else if c == '_' {
69 if !out.is_empty() && !out.ends_with('_') {
70 out.push('_');
71 }
72 } else {
73 out.push(c);
74 }
75 }
76
77 out
78}
79
80pub fn to_camel(s: &str) -> String {
82 let pascal = to_pascal(s);
83 let mut chars = pascal.chars();
84
85 match chars.next() {
86 None => String::new(),
87 Some(c) => c.to_lowercase().collect::<String>() + chars.as_str(),
88 }
89}
90
91pub fn to_screaming(s: &str) -> String {
93 to_snake(s).to_uppercase()
94}
95
96pub fn to_kebab(s: &str) -> String {
98 to_snake(s).replace('_', "-")
99}
100
101#[macro_export]
109macro_rules! pascal {
110 ($ident:expr => ident) => {
111 syn::Ident::new(
112 &$crate::case::to_pascal(&$ident.to_string()),
113 $ident.span(),
114 )
115 };
116 ($ts:expr => token_stream) => {{
117 let __tokens: Vec<proc_macro2::TokenTree> = $ts.clone().into_iter().collect();
118 let mut __out = proc_macro2::TokenStream::new();
119
120 for (i, __tt) in __tokens.iter().enumerate() {
121 match __tt {
122 proc_macro2::TokenTree::Ident(__ident) => {
123 let __is_last_ident = !__tokens[i + 1..]
124 .iter()
125 .any(|t| matches!(t, proc_macro2::TokenTree::Ident(_)));
126
127 if __is_last_ident {
128 quote::ToTokens::to_tokens(
129 &$crate::pascal!(__ident => ident),
130 &mut __out,
131 );
132 } else {
133 quote::ToTokens::to_tokens(__ident, &mut __out);
134 }
135 }
136 __other => {
137 quote::ToTokens::to_tokens(__other, &mut __out);
138 }
139 }
140 }
141
142 __out
143 }};
144 ($s:expr) => {
145 $crate::case::to_pascal($s)
146 };
147}
148
149#[macro_export]
154macro_rules! snake {
155 ($ident:expr => ident) => {
156 syn::Ident::new(&$crate::case::to_snake(&$ident.to_string()), $ident.span())
157 };
158 ($s:expr) => {
159 $crate::case::to_snake($s)
160 };
161}
162
163#[macro_export]
168macro_rules! camel {
169 ($ident:expr => ident) => {
170 syn::Ident::new(&$crate::case::to_camel(&$ident.to_string()), $ident.span())
171 };
172 ($s:expr) => {
173 $crate::case::to_camel($s)
174 };
175}
176
177#[macro_export]
182macro_rules! screaming {
183 ($ident:expr => ident) => {
184 syn::Ident::new(
185 &$crate::case::to_screaming(&$ident.to_string()),
186 $ident.span(),
187 )
188 };
189 ($s:expr) => {
190 $crate::case::to_screaming($s)
191 };
192}
193
194#[macro_export]
198macro_rules! kebab {
199 ($s:expr) => {
200 $crate::case::to_kebab($s)
201 };
202}