const_str/__ctfe/
squish.rs

1#![allow(unsafe_code)]
2
3use crate::__ctfe::StrBuf;
4
5pub struct Squish<T>(pub T);
6
7impl Squish<&'_ str> {
8    pub const fn output_len(&self) -> usize {
9        let mut len = 0;
10
11        macro_rules! push {
12            ($x: expr) => {
13                len += 1;
14            };
15        }
16
17        let bytes = self.0.as_bytes();
18        let mut i = 0;
19        while i < bytes.len() {
20            let x = bytes[i];
21
22            if x.is_ascii_whitespace() {
23                let mut j = i + 1;
24                while j < bytes.len() {
25                    if bytes[j].is_ascii_whitespace() {
26                        j += 1;
27                    } else {
28                        break;
29                    }
30                }
31                if !(i == 0 || j == bytes.len()) {
32                    push!(b' ');
33                }
34                i = j;
35                continue;
36            }
37
38            push!(x);
39            i += 1;
40        }
41
42        len
43    }
44
45    pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
46        let mut buf = [0; N];
47        let mut pos = 0;
48
49        macro_rules! push {
50            ($x: expr) => {
51                buf[pos] = $x;
52                pos += 1;
53            };
54        }
55
56        let bytes = self.0.as_bytes();
57        let mut i = 0;
58        while i < bytes.len() {
59            let x = bytes[i];
60
61            if x.is_ascii_whitespace() {
62                let mut j = i + 1;
63                while j < bytes.len() {
64                    if bytes[j].is_ascii_whitespace() {
65                        j += 1;
66                    } else {
67                        break;
68                    }
69                }
70                if !(i == 0 || j == bytes.len()) {
71                    push!(b' ');
72                }
73                i = j;
74                continue;
75            }
76
77            push!(x);
78            i += 1;
79        }
80
81        assert!(pos == N);
82        unsafe { StrBuf::new_unchecked(buf) }
83    }
84}
85
86/// Splits the string by ASCII whitespaces, and then joins the parts with a single space.
87///
88/// This macro is [const-context only](./index.html#const-context-only).
89///
90/// # Examples
91///
92/// ```rust
93/// use const_str::squish;
94///
95/// assert_eq!(squish!("   SQUISH  \t THAT  \t CAT!    "), "SQUISH THAT CAT!");
96///
97/// const SQL: &str = squish!(
98///     "SELECT
99///         name,
100///         created_at,
101///         updated_at
102///     FROM users
103///     WHERE id = ?"
104/// );
105/// assert_eq!(SQL, "SELECT name, created_at, updated_at FROM users WHERE id = ?");
106/// ```
107///
108#[macro_export]
109macro_rules! squish {
110    ($s:expr) => {{
111        const INPUT: &str = $s;
112        const N: usize = $crate::__ctfe::Squish(INPUT).output_len();
113        const OUTPUT: $crate::__ctfe::StrBuf<N> = $crate::__ctfe::Squish(INPUT).const_eval();
114        OUTPUT.as_str()
115    }};
116}
117
118#[cfg(test)]
119mod tessts {
120    fn join<'a>(iter: impl IntoIterator<Item = &'a str>, sep: &str) -> String {
121        let mut ans = String::new();
122        let mut iter = iter.into_iter();
123        match iter.next() {
124            None => return ans,
125            Some(first) => ans.push_str(first),
126        }
127        for part in iter {
128            ans.push_str(sep);
129            ans.push_str(part);
130        }
131        ans
132    }
133
134    fn std_squish(input: &str) -> String {
135        join(input.split_ascii_whitespace(), " ")
136    }
137
138    #[test]
139    fn test_squish() {
140        macro_rules! testcase {
141            ($s:expr) => {{
142                const OUTPUT: &str = squish!($s);
143                let expected = std_squish($s);
144                assert_eq!(OUTPUT, expected);
145            }};
146        }
147
148        testcase!("");
149        testcase!(" ");
150        testcase!(" t");
151        testcase!("t ");
152        testcase!(" t ");
153        testcase!(" t t");
154
155        testcase!(" SQUISH \t THAT \t CAT ");
156
157        testcase!(
158            "
159                All you need to know is to \t 
160                SQUISH THAT CAT! \
161            "
162        );
163
164        testcase!(concat!("We\n", "always\n", "SQUISH\n", "THAT\n", "CAT."));
165
166        testcase!(
167            "SELECT 
168                name, 
169                created_at, 
170                updated_at 
171            FROM users 
172            WHERE id = ?"
173        );
174    }
175}