const_format/
__ascii_case_conv.rs

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 = 0;
63    let mut length = 0;
64    let mut word_iter = 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 = words_count_and_length(s.as_bytes());
77            wcl.length
78        }
79        Case::Snake | Case::Kebab | Case::UpperSnake | Case::UpperKebab => {
80            let wcl = 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}