1pub fn to_pascal(s: &str) -> String {
6 let snake = to_snake(s);
7 let mut out = String::new();
8 let mut capitalize = true;
9
10 for c in snake.chars() {
11 if c == '_' {
12 capitalize = true;
13 } else if capitalize {
14 out.extend(c.to_uppercase());
15 capitalize = false;
16 } else {
17 out.push(c);
18 }
19 }
20
21 out
22}
23
24pub fn to_snake(s: &str) -> String {
26 let mut out = String::new();
27 let chars: Vec<char> = s.chars().collect();
28
29 for (i, &c) in chars.iter().enumerate() {
30 if c.is_uppercase() {
31 let prev_lower = i > 0 && chars[i - 1].is_lowercase();
32 let next_lower = i + 1 < chars.len() && chars[i + 1].is_lowercase();
33 let prev_upper = i > 0 && chars[i - 1].is_uppercase();
34
35 if prev_lower || (next_lower && prev_upper) {
36 out.push('_');
37 }
38
39 out.extend(c.to_lowercase());
40 } else if c == '_' {
41 if !out.is_empty() && !out.ends_with('_') {
42 out.push('_');
43 }
44 } else {
45 out.push(c);
46 }
47 }
48
49 out
50}
51
52pub fn to_camel(s: &str) -> String {
54 let pascal = to_pascal(s);
55 let mut chars = pascal.chars();
56
57 match chars.next() {
58 None => String::new(),
59 Some(c) => c.to_lowercase().collect::<String>() + chars.as_str(),
60 }
61}
62
63pub fn to_screaming(s: &str) -> String {
65 to_snake(s).to_uppercase()
66}
67
68pub fn to_kebab(s: &str) -> String {
70 to_snake(s).replace('_', "-")
71}
72
73#[macro_export]
81macro_rules! pascal {
82 ($ident:expr => ident) => {
83 syn::Ident::new(
84 &$crate::case::to_pascal(&$ident.to_string()),
85 $ident.span(),
86 )
87 };
88 ($ts:expr => token_stream) => {{
89 let __tokens: Vec<proc_macro2::TokenTree> = $ts.clone().into_iter().collect();
90 let mut __out = proc_macro2::TokenStream::new();
91
92 for (i, __tt) in __tokens.iter().enumerate() {
93 match __tt {
94 proc_macro2::TokenTree::Ident(__ident) => {
95 let __is_last_ident = !__tokens[i + 1..]
96 .iter()
97 .any(|t| matches!(t, proc_macro2::TokenTree::Ident(_)));
98
99 if __is_last_ident {
100 quote::ToTokens::to_tokens(
101 &$crate::pascal!(__ident => ident),
102 &mut __out,
103 );
104 } else {
105 quote::ToTokens::to_tokens(__ident, &mut __out);
106 }
107 }
108 __other => {
109 quote::ToTokens::to_tokens(__other, &mut __out);
110 }
111 }
112 }
113
114 __out
115 }};
116 ($s:expr) => {
117 $crate::case::to_pascal($s)
118 };
119}
120
121#[macro_export]
126macro_rules! snake {
127 ($ident:expr => ident) => {
128 syn::Ident::new(&$crate::case::to_snake(&$ident.to_string()), $ident.span())
129 };
130 ($s:expr) => {
131 $crate::case::to_snake($s)
132 };
133}
134
135#[macro_export]
140macro_rules! camel {
141 ($ident:expr => ident) => {
142 syn::Ident::new(&$crate::case::to_camel(&$ident.to_string()), $ident.span())
143 };
144 ($s:expr) => {
145 $crate::case::to_camel($s)
146 };
147}
148
149#[macro_export]
154macro_rules! screaming {
155 ($ident:expr => ident) => {
156 syn::Ident::new(
157 &$crate::case::to_screaming(&$ident.to_string()),
158 $ident.span(),
159 )
160 };
161 ($s:expr) => {
162 $crate::case::to_screaming($s)
163 };
164}
165
166#[macro_export]
170macro_rules! kebab {
171 ($s:expr) => {
172 $crate::case::to_kebab($s)
173 };
174}