Skip to main content

rustpython_wtf8/
concat.rs

1use alloc::borrow::{Cow, ToOwned};
2use alloc::boxed::Box;
3use alloc::string::String;
4use core::fmt;
5use fmt::Write;
6
7use crate::{CodePoint, Wtf8, Wtf8Buf};
8
9impl fmt::Write for Wtf8Buf {
10    #[inline]
11    fn write_str(&mut self, s: &str) -> fmt::Result {
12        self.push_str(s);
13        Ok(())
14    }
15}
16
17/// Trait for types that can be appended to a [`Wtf8Buf`], preserving surrogates.
18pub trait Wtf8Concat {
19    fn fmt_wtf8(&self, buf: &mut Wtf8Buf);
20}
21
22impl Wtf8Concat for Wtf8 {
23    #[inline]
24    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
25        buf.push_wtf8(self);
26    }
27}
28
29impl Wtf8Concat for Wtf8Buf {
30    #[inline]
31    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
32        buf.push_wtf8(self);
33    }
34}
35
36impl Wtf8Concat for str {
37    #[inline]
38    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
39        buf.push_str(self);
40    }
41}
42
43impl Wtf8Concat for String {
44    #[inline]
45    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
46        buf.push_str(self);
47    }
48}
49
50impl Wtf8Concat for char {
51    #[inline]
52    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
53        buf.push_char(*self);
54    }
55}
56
57impl Wtf8Concat for CodePoint {
58    #[inline]
59    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
60        buf.push(*self);
61    }
62}
63
64/// Wrapper that appends a [`fmt::Display`] value to a [`Wtf8Buf`].
65///
66/// Note: This goes through UTF-8 formatting, so lone surrogates in the
67/// display output will be replaced with U+FFFD. Use direct [`Wtf8Concat`]
68/// impls for surrogate-preserving concatenation.
69#[allow(dead_code)]
70pub struct DisplayAsWtf8<T>(pub T);
71
72impl<T: fmt::Display> Wtf8Concat for DisplayAsWtf8<T> {
73    #[inline]
74    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
75        write!(buf, "{}", self.0).unwrap();
76    }
77}
78
79macro_rules! impl_wtf8_concat_for_int {
80    ($($t:ty),*) => {
81        $(impl Wtf8Concat for $t {
82            #[inline]
83            fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
84                write!(buf, "{}", self).unwrap();
85            }
86        })*
87    };
88}
89
90impl_wtf8_concat_for_int!(
91    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64
92);
93
94impl<T: Wtf8Concat + ?Sized> Wtf8Concat for &T {
95    #[inline]
96    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
97        (**self).fmt_wtf8(buf);
98    }
99}
100
101impl<T: Wtf8Concat + ?Sized> Wtf8Concat for &mut T {
102    #[inline]
103    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
104        (**self).fmt_wtf8(buf);
105    }
106}
107
108impl<T: Wtf8Concat + ?Sized> Wtf8Concat for Box<T> {
109    #[inline]
110    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
111        (**self).fmt_wtf8(buf);
112    }
113}
114
115impl<T: Wtf8Concat + ?Sized> Wtf8Concat for Cow<'_, T>
116where
117    T: ToOwned,
118{
119    #[inline]
120    fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
121        (**self).fmt_wtf8(buf);
122    }
123}
124
125/// Concatenate values into a [`Wtf8Buf`], preserving surrogates.
126///
127/// Each argument must implement [`Wtf8Concat`]. String literals (`&str`),
128/// [`Wtf8`], [`Wtf8Buf`], [`char`], and [`CodePoint`] are all supported.
129///
130/// ```
131/// use rustpython_wtf8::Wtf8Buf;
132/// let name = "world";
133/// let result = rustpython_wtf8::wtf8_concat!("hello, ", name, "!");
134/// assert_eq!(result, Wtf8Buf::from("hello, world!"));
135/// ```
136#[macro_export]
137macro_rules! wtf8_concat {
138    ($($arg:expr),* $(,)?) => {{
139        let mut buf = $crate::Wtf8Buf::new();
140        $($crate::Wtf8Concat::fmt_wtf8(&$arg, &mut buf);)*
141        buf
142    }};
143}