1mod word_iterator;
2
3use 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)]
13pub 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
32macro_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
43macro_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
54struct WordCountAndLength {
55 /// The amount of words
56 count: usize,
57 /// The length of all words added up
58 length: usize,
59}
60
61const 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
72pub 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
86pub 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
169const CASE_DIFF: u8 = b'a' - b'A';
170
171const 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
179const 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