1 | mod word_iterator; |
2 | |
3 | use word_iterator::WordIterator; |
4 | |
5 | /// The casing style of a string. |
6 | /// |
7 | /// You can pass this to [`map_ascii_case`] to determine the casing style of the |
8 | /// returned `&'static str`. |
9 | /// |
10 | /// |
11 | /// [`map_ascii_case`]: ./macro.map_ascii_case.html |
12 | #[derive (Debug, Copy, Clone, PartialEq)] |
13 | pub enum Case { |
14 | /// Lowercase |
15 | Lower, |
16 | /// Uppercase |
17 | Upper, |
18 | /// Pascal case, eg: `FooBarBaz`. The first character is always uppercase. |
19 | Pascal, |
20 | /// Camel case, eg: `fooBarBaz`. The first character is always lowercase. |
21 | Camel, |
22 | /// Snake case, eg: `foo_bar_baz`. Also turns the string lowercase. |
23 | Snake, |
24 | /// Snake case, eg: `FOO_BAR_BAZ`. Also turns the string uppercase. |
25 | UpperSnake, |
26 | /// Kebab case, eg: `foo-bar-baz`. Also turns the string lowercase. |
27 | Kebab, |
28 | /// Kebab case, eg: `FOO-BAR-BAZ`. Also turns the string uppercase. |
29 | UpperKebab, |
30 | } |
31 | |
32 | macro_rules! if_next_word { |
33 | ($word_iterator:ident, $word_range:ident => $then:block $(else $else:block)? ) => { |
34 | #[allow(unused_mut)] |
35 | if let Some((niter, mut $word_range)) = $word_iterator.next() { |
36 | $word_iterator = niter; |
37 | |
38 | $then |
39 | } $(else $else)? |
40 | }; |
41 | } |
42 | |
43 | macro_rules! while_next_word { |
44 | ($word_iterator:ident, $word_range:ident => $then:block) => { |
45 | #[allow(unused_mut)] |
46 | while let Some((niter, mut $word_range)) = $word_iterator.next() { |
47 | $word_iterator = niter; |
48 | |
49 | $then |
50 | } |
51 | }; |
52 | } |
53 | |
54 | struct WordCountAndLength { |
55 | /// The amount of words |
56 | count: usize, |
57 | /// The length of all words added up |
58 | length: usize, |
59 | } |
60 | |
61 | const fn words_count_and_length(bytes: &[u8]) -> WordCountAndLength { |
62 | let mut count: usize = 0; |
63 | let mut length: usize = 0; |
64 | let mut word_iter: WordIterator<'_> = WordIterator::new(bytes); |
65 | while_next_word! {word_iter, word_range => { |
66 | count += 1; |
67 | length += word_range.end - word_range.start; |
68 | }} |
69 | WordCountAndLength { count, length } |
70 | } |
71 | |
72 | pub const fn size_after_conversion(case: Case, s: &str) -> usize { |
73 | match case { |
74 | Case::Upper | Case::Lower => s.len(), |
75 | Case::Pascal | Case::Camel => { |
76 | let wcl: WordCountAndLength = words_count_and_length(s.as_bytes()); |
77 | wcl.length |
78 | } |
79 | Case::Snake | Case::Kebab | Case::UpperSnake | Case::UpperKebab => { |
80 | let wcl: WordCountAndLength = words_count_and_length(s.as_bytes()); |
81 | wcl.length + wcl.count.saturating_sub(1) |
82 | } |
83 | } |
84 | } |
85 | |
86 | pub const fn convert_str<const N: usize>(case: Case, s: &str) -> [u8; N] { |
87 | let mut arr = [0; N]; |
88 | let mut inp = s.as_bytes(); |
89 | let mut o = 0; |
90 | |
91 | macro_rules! map_bytes { |
92 | ($byte:ident => $e:expr) => { |
93 | while let [$byte, rem @ ..] = inp { |
94 | let $byte = *$byte; |
95 | inp = rem; |
96 | arr[o] = $e; |
97 | o += 1; |
98 | } |
99 | }; |
100 | } |
101 | |
102 | macro_rules! write_byte { |
103 | ($byte:expr) => { |
104 | arr[o] = $byte; |
105 | o += 1; |
106 | }; |
107 | } |
108 | |
109 | macro_rules! write_range_from { |
110 | ($range:expr, $from:expr, $byte:ident => $mapper:expr) => {{ |
111 | let mut range = $range; |
112 | while range.start < range.end { |
113 | let $byte = $from[range.start]; |
114 | arr[o] = $mapper; |
115 | |
116 | range.start += 1; |
117 | o += 1; |
118 | } |
119 | }}; |
120 | } |
121 | |
122 | macro_rules! write_snake_kebab_case { |
123 | ($separator:expr, $byte_conversion:expr) => {{ |
124 | let mut word_iter = WordIterator::new(inp); |
125 | |
126 | if_next_word! {word_iter, word_range => { |
127 | write_range_from!(word_range, inp, byte => $byte_conversion(byte)); |
128 | |
129 | while_next_word!{word_iter, word_range => { |
130 | write_byte!($separator); |
131 | write_range_from!(word_range, inp, byte => $byte_conversion(byte)); |
132 | }} |
133 | }} |
134 | }}; |
135 | } |
136 | |
137 | macro_rules! write_pascal_camel_case { |
138 | ($first_word_conv:expr) => {{ |
139 | let mut word_iter = WordIterator::new(inp); |
140 | |
141 | if_next_word! {word_iter, word_range => { |
142 | write_byte!($first_word_conv(inp[word_range.start])); |
143 | word_range.start += 1; |
144 | write_range_from!(word_range, inp, byte => lowercase_u8(byte)); |
145 | |
146 | while_next_word!{word_iter, word_range => { |
147 | write_byte!(uppercase_u8(inp[word_range.start])); |
148 | word_range.start += 1; |
149 | write_range_from!(word_range, inp, byte => lowercase_u8(byte)); |
150 | }} |
151 | }} |
152 | }}; |
153 | } |
154 | |
155 | match case { |
156 | Case::Upper => map_bytes!(b => uppercase_u8(b)), |
157 | Case::Lower => map_bytes!(b => lowercase_u8(b)), |
158 | Case::Snake => write_snake_kebab_case!(b'_' , lowercase_u8), |
159 | Case::UpperSnake => write_snake_kebab_case!(b'_' , uppercase_u8), |
160 | Case::Kebab => write_snake_kebab_case!(b'-' , lowercase_u8), |
161 | Case::UpperKebab => write_snake_kebab_case!(b'-' , uppercase_u8), |
162 | Case::Pascal => write_pascal_camel_case!(uppercase_u8), |
163 | Case::Camel => write_pascal_camel_case!(lowercase_u8), |
164 | } |
165 | |
166 | arr |
167 | } |
168 | |
169 | const CASE_DIFF: u8 = b'a' - b'A' ; |
170 | |
171 | const fn uppercase_u8(b: u8) -> u8 { |
172 | if let b'a' ..=b'z' = b { |
173 | b - CASE_DIFF |
174 | } else { |
175 | b |
176 | } |
177 | } |
178 | |
179 | const fn lowercase_u8(b: u8) -> u8 { |
180 | if let b'A' ..=b'Z' = b { |
181 | b + CASE_DIFF |
182 | } else { |
183 | b |
184 | } |
185 | } |
186 | |