1#![expect(unsafe_code)]
6
7use std::borrow;
8use std::fmt::Write;
9use std::fmt::{self};
10use std::ops;
11use std::str;
12
13#[derive(Clone, Hash, PartialEq, Eq, Default)]
17pub struct LxString {
18 bytes: Vec<u8>,
19}
20
21impl LxString {
22 pub fn new() -> Self {
24 Self { bytes: Vec::new() }
25 }
26
27 pub fn from_vec(vec: Vec<u8>) -> Self {
29 Self { bytes: vec }
30 }
31
32 pub fn with_capacity(capacity: usize) -> Self {
34 Self {
35 bytes: Vec::with_capacity(capacity),
36 }
37 }
38
39 pub fn into_vec(self) -> Vec<u8> {
41 self.bytes
42 }
43
44 pub fn into_string(self) -> Result<String, Self> {
46 String::from_utf8(self.bytes).map_err(|e| Self {
47 bytes: e.into_bytes(),
48 })
49 }
50
51 pub fn as_lx_str(&self) -> &LxStr {
53 self
54 }
55
56 pub fn capacity(&self) -> usize {
58 self.bytes.capacity()
59 }
60
61 pub fn clear(&mut self) {
63 self.bytes.clear()
64 }
65
66 pub fn push(&mut self, s: impl AsRef<LxStr>) {
68 self.bytes.extend_from_slice(&s.as_ref().bytes)
69 }
70
71 pub fn reserve(&mut self, additional: usize) {
73 self.bytes.reserve(additional);
74 }
75
76 pub fn reserve_exact(&mut self, additional: usize) {
79 self.bytes.reserve_exact(additional);
80 }
81
82 pub fn shrink_to_fit(&mut self) {
84 self.bytes.shrink_to_fit()
85 }
86}
87
88impl ops::Deref for LxString {
89 type Target = LxStr;
90
91 fn deref(&self) -> &Self::Target {
92 LxStr::from_bytes(&self.bytes)
93 }
94}
95
96impl borrow::Borrow<LxStr> for LxString {
97 fn borrow(&self) -> &LxStr {
98 self
99 }
100}
101
102impl<T> From<&T> for LxString
103where
104 T: ?Sized + AsRef<LxStr>,
105{
106 fn from(s: &T) -> Self {
107 s.as_ref().to_lx_string()
108 }
109}
110
111impl From<String> for LxString {
112 fn from(s: String) -> Self {
113 LxString::from_vec(s.into())
114 }
115}
116
117impl PartialEq<LxString> for &str {
118 fn eq(&self, other: &LxString) -> bool {
119 **self == **other
120 }
121}
122
123impl PartialEq<LxString> for str {
124 fn eq(&self, other: &LxString) -> bool {
125 &**other == self
126 }
127}
128
129impl PartialEq<str> for LxString {
130 fn eq(&self, other: &str) -> bool {
131 &**self == other
132 }
133}
134
135impl PartialEq<&str> for LxString {
136 fn eq(&self, other: &&str) -> bool {
137 **self == **other
138 }
139}
140
141impl fmt::Debug for LxString {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 fmt::Debug::fmt(&**self, f)
144 }
145}
146
147#[derive(PartialEq, Eq, Hash)]
151#[repr(transparent)]
152pub struct LxStr {
153 bytes: [u8],
154}
155
156impl LxStr {
157 pub fn new<T: AsRef<LxStr> + ?Sized>(s: &T) -> &LxStr {
159 s.as_ref()
160 }
161
162 pub fn from_bytes(slice: &[u8]) -> &LxStr {
164 unsafe { std::mem::transmute(slice) }
167 }
168
169 pub fn as_bytes(&self) -> &[u8] {
171 &self.bytes
172 }
173
174 pub fn to_lx_string(&self) -> LxString {
176 LxString::from_vec(self.bytes.into())
177 }
178
179 pub fn len(&self) -> usize {
181 self.bytes.len()
182 }
183
184 pub fn is_empty(&self) -> bool {
186 self.bytes.is_empty()
187 }
188
189 pub fn to_str(&self) -> Option<&str> {
191 str::from_utf8(&self.bytes).ok()
192 }
193
194 pub fn to_string_lossy(&self) -> borrow::Cow<'_, str> {
198 String::from_utf8_lossy(&self.bytes)
199 }
200}
201
202impl ToOwned for LxStr {
203 type Owned = LxString;
204
205 fn to_owned(&self) -> Self::Owned {
206 self.to_lx_string()
207 }
208}
209
210impl AsRef<LxStr> for LxStr {
211 fn as_ref(&self) -> &LxStr {
212 self
213 }
214}
215
216impl AsRef<LxStr> for LxString {
217 fn as_ref(&self) -> &LxStr {
218 self
219 }
220}
221
222impl AsRef<LxStr> for str {
223 fn as_ref(&self) -> &LxStr {
224 LxStr::from_bytes(self.as_bytes())
225 }
226}
227
228impl AsRef<LxStr> for String {
229 fn as_ref(&self) -> &LxStr {
230 (**self).as_ref()
231 }
232}
233
234impl PartialEq<LxStr> for str {
235 fn eq(&self, other: &LxStr) -> bool {
236 LxStr::new(self).eq(other)
237 }
238}
239
240impl PartialEq<str> for LxStr {
241 fn eq(&self, other: &str) -> bool {
242 self.eq(LxStr::new(other))
243 }
244}
245
246impl fmt::Debug for LxStr {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 let value = self.to_string_lossy();
251 f.write_str("\"")?;
252 for c in value.chars().flat_map(|c| c.escape_debug()) {
253 f.write_char(c)?
254 }
255
256 f.write_str("\"")
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn lx_string_new() {
266 let s = LxString::new();
267 assert_eq!(s.capacity(), 0);
268 assert_eq!(s.len(), 0);
269 assert!(s.is_empty());
270 assert_eq!(s, "");
271 assert_ne!(s, "something");
272 }
273
274 #[test]
275 fn lx_string_capacity() {
276 let s = LxString::with_capacity(100);
277 assert_eq!(s.capacity(), 100);
278 assert_eq!(s.len(), 0);
279 assert!(s.is_empty());
280 assert_eq!(s, "");
281 assert_ne!(s, "something");
282 }
283
284 #[test]
285 fn lx_string_from() {
286 let s = LxString::from("foo");
287 assert_eq!(s.capacity(), 3);
288 assert_eq!(s.len(), 3);
289 assert!(!s.is_empty());
290 assert_eq!(s, "foo");
291 assert_ne!(s, "something");
292 assert_ne!(s, "");
293
294 let s = LxString::from(String::from("foo"));
295 assert_eq!(s.capacity(), 3);
296 assert_eq!(s.len(), 3);
297 assert!(!s.is_empty());
298 assert_eq!(s, "foo");
299 assert_ne!(s, "something");
300 assert_ne!(s, "");
301 }
302
303 #[test]
304 fn lx_string_from_vec() {
305 let s = LxString::from_vec(b"foo".to_vec());
306 assert_eq!(s.capacity(), 3);
307 assert_eq!(s.len(), 3);
308 assert!(!s.is_empty());
309 assert_eq!(s, "foo");
310 assert_ne!(s, "something");
311 assert_ne!(s, "");
312 let vec = s.into_vec();
313 assert_eq!(vec, b"foo");
314 }
315
316 #[test]
317 fn lx_string_into_string() {
318 let s = LxString::from("foo");
319 let s = s.into_string().unwrap();
320 assert_eq!(s, "foo");
321
322 let s = LxString::from_vec(vec![b'a', 0xfe, 0xfe]);
323 let e = s.into_string().unwrap_err();
324 assert_eq!(e, LxString::from_vec(vec![b'a', 0xfe, 0xfe]));
325 }
326
327 #[test]
328 fn lx_string_debug() {
329 let s = LxString::from("foo\\bar");
330 let debug = format!("{:?}", s);
331 assert_eq!(debug, r#""foo\\bar""#)
332 }
333
334 #[test]
335 fn lx_str_new() {
336 let s = LxStr::new("");
337 assert_eq!(s.len(), 0);
338 assert!(s.is_empty());
339 assert_eq!(s, "");
340 assert_ne!(s, "something");
341
342 let s = LxStr::new("foo");
343 assert_eq!(s.len(), 3);
344 assert!(!s.is_empty());
345 assert_eq!(s, "foo");
346 assert_eq!(s, LxStr::new("foo"));
347 assert_ne!(s, "something");
348 assert_ne!(s, "");
349 }
350
351 #[test]
352 fn lx_str_from_bytes() {
353 let s = LxStr::from_bytes(b"foo");
354 assert_eq!(s.len(), 3);
355 assert!(!s.is_empty());
356 assert_eq!(s, "foo");
357 assert_ne!(s, "something");
358 assert_ne!(s, "");
359 }
360
361 #[test]
362 fn lx_str_from_lx_string() {
363 let s = LxString::from("foo");
364 let s = s.as_lx_str();
365 assert_eq!(s.len(), 3);
366 assert!(!s.is_empty());
367 assert_eq!(s, "foo");
368 assert_ne!(s, "something");
369 assert_ne!(s, "");
370 }
371
372 #[test]
373 fn lx_str_to_str() {
374 let s = LxStr::new("foo");
375 let s = s.to_str().unwrap();
376 assert_eq!(s, "foo");
377
378 let s = LxStr::from_bytes(&[b'a', 0xfe, 0xfe]);
379 assert!(s.to_str().is_none());
380 }
381
382 #[test]
383 fn lx_str_to_string_lossy() {
384 let s = LxStr::new("foo");
385 let s = s.to_string_lossy();
386 assert!(matches!(s, borrow::Cow::Borrowed(_)));
387 assert_eq!(s, "foo");
388 let s = LxStr::from_bytes(&[b'a', 0xfe, 0xfe, b'b']);
389
390 let s = s.to_string_lossy();
391 assert!(matches!(s, borrow::Cow::Owned(_)));
392 assert_eq!(s, "a\u{fffd}\u{fffd}b");
393 }
394
395 #[test]
396 fn lx_str_debug() {
397 let s = LxStr::new("foo\\bar");
398 let debug = format!("{:?}", s);
399 assert_eq!(debug, r#""foo\\bar""#)
400 }
401}