1use core::fmt;
2use core::mem::take;
3
4use crate::expander::{NameMethod, UnsizedMethod};
5
6#[derive(Default, Debug, Clone, Copy)]
7#[allow(clippy::enum_variant_names)]
8pub(crate) enum NameAll {
9 #[default]
11 Index,
12 Name,
14 PascalCase,
15 CamelCase,
16 SnakeCase,
17 ScreamingSnakeCase,
18 KebabCase,
19 ScreamingKebabCase,
20}
21
22impl NameAll {
23 pub(crate) const ALL: &'static [Self] = &[
24 Self::Index,
25 Self::Name,
26 Self::PascalCase,
27 Self::CamelCase,
28 Self::SnakeCase,
29 Self::ScreamingSnakeCase,
30 Self::KebabCase,
31 Self::ScreamingKebabCase,
32 ];
33
34 pub(crate) fn ty(&self) -> syn::Type {
35 match self {
36 NameAll::Index => syn::parse_quote! { usize },
37 _ => syn::parse_quote! { str },
38 }
39 }
40
41 pub(crate) fn name_method(&self) -> NameMethod {
42 match self {
43 NameAll::Index => NameMethod::Value,
44 _ => NameMethod::Unsized(UnsizedMethod::Default),
45 }
46 }
47
48 pub(crate) fn parse(input: &str) -> Option<Self> {
49 match input {
50 "index" => Some(Self::Index),
51 "name" => Some(Self::Name),
52 "PascalCase" => Some(Self::PascalCase),
53 "camelCase" => Some(Self::CamelCase),
54 "snake_case" => Some(Self::SnakeCase),
55 "SCREAMING_SNAKE_CASE" => Some(Self::ScreamingSnakeCase),
56 "kebab-case" => Some(Self::KebabCase),
57 "SCREAMING-KEBAB-CASE" => Some(Self::ScreamingKebabCase),
58 _ => None,
59 }
60 }
61
62 pub(crate) fn apply(&self, input: &str) -> Option<String> {
64 let feed: fn(output: &mut String, open: bool, count: usize, c: char) = match self {
65 Self::Index => return None,
66 Self::Name => return Some(input.to_string()),
67 Self::PascalCase => |output, open, _, c| {
68 if open {
69 output.extend(c.to_uppercase());
70 } else {
71 output.extend(c.to_lowercase());
72 }
73 },
74 Self::CamelCase => |output, open, count, c| {
75 if open && count > 0 {
76 output.extend(c.to_uppercase());
77 } else {
78 output.extend(c.to_lowercase());
79 }
80 },
81 Self::SnakeCase => |output, _, _, c| {
82 output.extend(c.to_lowercase());
83 },
84 Self::ScreamingSnakeCase => |output, _, _, c| {
85 output.extend(c.to_uppercase());
86 },
87 Self::KebabCase => |output, _, _, c| {
88 output.extend(c.to_lowercase());
89 },
90 Self::ScreamingKebabCase => |output, _, _, c| {
91 output.extend(c.to_uppercase());
92 },
93 };
94
95 let prefix = match self {
96 Self::SnakeCase => Some('_'),
97 Self::ScreamingSnakeCase => Some('_'),
98 Self::KebabCase => Some('-'),
99 Self::ScreamingKebabCase => Some('-'),
100 _ => None,
101 };
102
103 let mut output = String::new();
104 let mut count = 0;
105 let mut group = true;
106
107 for c in input.chars() {
108 if matches!(c, '_') {
109 if !group {
110 count += 1;
111 }
112
113 group = true;
114 continue;
115 }
116
117 if char::is_uppercase(c) && !group {
118 group = true;
119 count += 1;
120 }
121
122 let group = take(&mut group);
123
124 if group && count > 0 {
125 output.extend(prefix);
126 }
127
128 feed(&mut output, group, count, c);
129 }
130
131 Some(output)
132 }
133}
134
135impl fmt::Display for NameAll {
136 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137 match self {
138 Self::Index => write!(f, "index"),
139 Self::Name => write!(f, "name"),
140 Self::PascalCase => write!(f, "PascalCase"),
141 Self::CamelCase => write!(f, "camelCase"),
142 Self::SnakeCase => write!(f, "snake_case"),
143 Self::ScreamingSnakeCase => write!(f, "SCREAMING_SNAKE_CASE"),
144 Self::KebabCase => write!(f, "kebab-case"),
145 Self::ScreamingKebabCase => write!(f, "SCREAMING-KEBAB-CASE"),
146 }
147 }
148}
149
150#[test]
151fn test_rename() {
152 #[track_caller]
153 fn test(input: &str, rename: &str, expected: &str) {
154 let rename = NameAll::parse(rename).unwrap();
155 assert_eq!(rename.apply(input).unwrap(), expected);
156 }
157
158 test("hello_world", "PascalCase", "HelloWorld");
159 test("__hello__world__", "PascalCase", "HelloWorld");
160 test("hello_world", "camelCase", "helloWorld");
161 test("__hello__world__", "camelCase", "helloWorld");
162 test("hello_world", "snake_case", "hello_world");
163 test("__hello__world__", "snake_case", "hello_world");
164 test("hello_world", "SCREAMING_SNAKE_CASE", "HELLO_WORLD");
165 test("__hello__world__", "SCREAMING_SNAKE_CASE", "HELLO_WORLD");
166 test("hello_world", "kebab-case", "hello-world");
167 test("__hello__world__", "kebab-case", "hello-world");
168 test("hello_world", "SCREAMING-KEBAB-CASE", "HELLO-WORLD");
169 test("__hello__world__", "SCREAMING-KEBAB-CASE", "HELLO-WORLD");
170
171 test("HelloWorld", "PascalCase", "HelloWorld");
172 test("__Hello__World__", "PascalCase", "HelloWorld");
173 test("HelloWorld", "camelCase", "helloWorld");
174 test("__Hello__World__", "camelCase", "helloWorld");
175 test("HelloWorld", "snake_case", "hello_world");
176 test("__Hello__World__", "snake_case", "hello_world");
177 test("HelloWorld", "SCREAMING_SNAKE_CASE", "HELLO_WORLD");
178 test("__Hello__World__", "SCREAMING_SNAKE_CASE", "HELLO_WORLD");
179 test("HelloWorld", "kebab-case", "hello-world");
180 test("__Hello__World__", "kebab-case", "hello-world");
181 test("HelloWorld", "SCREAMING-KEBAB-CASE", "HELLO-WORLD");
182 test("__Hello__World__", "SCREAMING-KEBAB-CASE", "HELLO-WORLD");
183}