| 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 | |