lstring/
format.rs

1use crate::{backend::HeapStr, KString, KStringCow, KStringRef, StackString};
2use core::fmt;
3use std::{borrow::Cow, string::String as StdString};
4
5/// A macro for formatting arguments into a `KString`.
6///
7/// This macro is similar to Rust's standard `format!` macro but outputs a `KString`
8/// instead of a `String`. It uses the `fmt::Write` trait internally to append formatted
9/// text efficiently.
10///
11/// # Examples
12///
13/// ```
14/// # use lstring::{kformat, KString};
15/// let s: KString = kformat!("Hello, {}!", "world");
16/// assert_eq!(s, "Hello, world!");
17/// ```
18#[macro_export]
19macro_rules! kformat {
20    ($($arg:tt)*) => {{
21        use core::fmt::Write;
22        let mut ks = $crate::KStringWriter::default();
23        write!(ks, $($arg)*).unwrap();
24        ks.into()
25    }};
26}
27
28/// A writer that can append formatted text to either an inline `StackString` or a heap-allocated `String`.
29///
30/// The `KStringWriter` starts writing into a stack-allocated `StackString` for efficiency. If the capacity
31/// of the inline storage is exceeded, it switches to using a heap-allocated `String`. This allows for
32/// efficient formatting without unnecessary allocations in cases where the formatted text fits within the
33/// inline buffer.
34///
35/// # Examples
36///
37/// ```
38/// use lstring::{KString, KStringWriter};
39/// use core::fmt::Write;
40///
41/// let mut writer = KStringWriter::default();
42/// write!(writer, "Hello, {}!", "world").unwrap();
43/// let s: KString = writer.into();
44/// assert_eq!(s, "Hello, world!");
45/// ```
46#[derive(Clone)]
47pub enum KStringWriter {
48    Inline(StackString<{ KString::MAX_INLINE_LEN }>),
49    Owned(String),
50}
51
52impl KStringWriter {
53    pub fn new() -> Self {
54        Self::Inline(StackString::default())
55    }
56
57    /// Reserves additional capacity for the `KStringWriter`.
58    ///
59    /// If the writer is currently using an inline `StackString` and the requested capacity exceeds
60    /// its maximum inline length, it will switch to a heap-allocated `String`. This method ensures
61    /// that subsequent writes do not require reallocations if the specified capacity is available.
62    pub fn reserve(&mut self, additional: usize) {
63        match self {
64            KStringWriter::Inline(inline) => {
65                let capacity = inline.len() + additional;
66                if capacity > KString::MAX_INLINE_LEN {
67                    // Turn to owned
68                    let mut owned = String::with_capacity(capacity);
69                    owned.push_str(inline);
70                    *self = KStringWriter::Owned(owned);
71                }
72            }
73            KStringWriter::Owned(owned) => {
74                owned.reserve(additional);
75            }
76        }
77    }
78
79    /// Appends a single character to the `KStringWriter`.
80    ///
81    /// # Examples
82    ///
83    /// ```
84    /// use lstring::KStringWriter;
85    ///
86    /// let mut writer = KStringWriter::default();
87    /// writer.push('a');
88    /// assert_eq!(writer.as_ref(), "a");
89    /// ```
90    pub fn push(&mut self, ch: char) {
91        if let Self::Inline(inline) = self {
92            if inline.try_push_char(ch) {
93                return;
94            }
95            *self = Self::Owned(String::from(&*inline));
96        }
97        if let Self::Owned(owned) = self {
98            owned.push(ch);
99        } else {
100            unreachable!();
101        }
102    }
103
104    /// Appends a string slice to the `KStringWriter`.
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use lstring::KStringWriter;
110    ///
111    /// let mut writer = KStringWriter::default();
112    /// writer.push_str("abc");
113    /// assert_eq!(writer.as_ref(), "abc");
114    /// ```
115    pub fn push_str(&mut self, s: &str) {
116        if let Self::Inline(inline) = self {
117            if inline.try_push(s) {
118                return;
119            }
120            *self = Self::Owned(String::from(&*inline));
121        }
122        if let Self::Owned(owned) = self {
123            owned.push_str(s);
124        } else {
125            unreachable!();
126        }
127    }
128}
129
130impl AsRef<str> for KStringWriter {
131    fn as_ref(&self) -> &str {
132        match self {
133            Self::Inline(inline) => inline.as_ref(),
134            Self::Owned(owned) => owned.as_ref(),
135        }
136    }
137}
138
139impl Default for KStringWriter {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145impl<B: HeapStr> From<KStringWriter> for KString<B> {
146    fn from(writer: KStringWriter) -> Self {
147        match writer {
148            KStringWriter::Inline(inline) => inline.into(),
149            KStringWriter::Owned(owned) => owned.into(),
150        }
151    }
152}
153
154impl fmt::Write for KStringWriter {
155    fn write_str(&mut self, s: &str) -> fmt::Result {
156        self.push_str(s);
157        Ok(())
158    }
159}
160
161impl Extend<char> for KStringWriter {
162    fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
163        let iterator = iter.into_iter();
164        let (lower_bound, _) = iterator.size_hint();
165        self.reserve(lower_bound);
166        for c in iterator {
167            self.push(c);
168        }
169    }
170}
171
172impl<'a> Extend<&'a char> for KStringWriter {
173    fn extend<I: IntoIterator<Item = &'a char>>(&mut self, iter: I) {
174        self.extend(iter.into_iter().cloned());
175    }
176}
177
178impl<'a> Extend<&'a str> for KStringWriter {
179    fn extend<I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
180        for s in iter.into_iter() {
181            self.push_str(s);
182        }
183    }
184}
185
186impl Extend<Box<str>> for KStringWriter {
187    fn extend<I: IntoIterator<Item = Box<str>>>(&mut self, iter: I) {
188        for s in iter.into_iter() {
189            self.push_str(&s);
190        }
191    }
192}
193
194impl Extend<StdString> for KStringWriter {
195    fn extend<I: IntoIterator<Item = String>>(&mut self, iter: I) {
196        for s in iter.into_iter() {
197            self.push_str(&s);
198        }
199    }
200}
201
202impl<'a> Extend<Cow<'a, str>> for KStringWriter {
203    fn extend<I: IntoIterator<Item = Cow<'a, str>>>(&mut self, iter: I) {
204        for s in iter.into_iter() {
205            self.push_str(&s);
206        }
207    }
208}
209
210impl<'a, B: HeapStr> Extend<&'a KString<B>> for KStringWriter {
211    fn extend<I: IntoIterator<Item = &'a KString<B>>>(&mut self, iter: I) {
212        for s in iter.into_iter() {
213            self.push_str(s);
214        }
215    }
216}
217
218impl<B: HeapStr> Extend<KString<B>> for KStringWriter {
219    fn extend<I: IntoIterator<Item = KString<B>>>(&mut self, iter: I) {
220        for s in iter.into_iter() {
221            self.push_str(&s);
222        }
223    }
224}
225
226impl<'a, 's, B: HeapStr> Extend<&'a KStringCow<'s, B>> for KStringWriter {
227    fn extend<I: IntoIterator<Item = &'a KStringCow<'s, B>>>(&mut self, iter: I) {
228        for s in iter.into_iter() {
229            self.push_str(s);
230        }
231    }
232}
233
234impl<'s, B: HeapStr> Extend<KStringCow<'s, B>> for KStringWriter {
235    fn extend<I: IntoIterator<Item = KStringCow<'s, B>>>(&mut self, iter: I) {
236        for s in iter.into_iter() {
237            self.push_str(&s);
238        }
239    }
240}
241
242impl<'a, 's> Extend<&'a KStringRef<'s>> for KStringWriter {
243    fn extend<I: IntoIterator<Item = &'a KStringRef<'s>>>(&mut self, iter: I) {
244        for s in iter.into_iter() {
245            self.push_str(s);
246        }
247    }
248}
249
250impl<'s> Extend<KStringRef<'s>> for KStringWriter {
251    fn extend<I: IntoIterator<Item = KStringRef<'s>>>(&mut self, iter: I) {
252        for s in iter.into_iter() {
253            self.push_str(&s);
254        }
255    }
256}
257
258impl<B: HeapStr> FromIterator<char> for KString<B> {
259    fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
260        let mut writer = KStringWriter::new();
261        writer.extend(iter);
262        writer.into()
263    }
264}
265
266impl<'a, B: HeapStr> FromIterator<&'a char> for KString<B> {
267    fn from_iter<I: IntoIterator<Item = &'a char>>(iter: I) -> Self {
268        let mut writer = KStringWriter::new();
269        writer.extend(iter);
270        writer.into()
271    }
272}
273
274impl<'a, B: HeapStr> FromIterator<&'a str> for KString<B> {
275    fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Self {
276        let mut writer = KStringWriter::new();
277        writer.extend(iter);
278        writer.into()
279    }
280}
281
282impl<B: HeapStr> FromIterator<Box<str>> for KString<B> {
283    fn from_iter<I: IntoIterator<Item = Box<str>>>(iter: I) -> KString<B> {
284        let mut writer = KStringWriter::new();
285        writer.extend(iter);
286        writer.into()
287    }
288}
289
290impl<'a, B: HeapStr> FromIterator<Cow<'a, str>> for KString<B> {
291    fn from_iter<I: IntoIterator<Item = Cow<'a, str>>>(iter: I) -> KString<B> {
292        let mut iterator = iter.into_iter();
293
294        match iterator.next() {
295            None => KString::new(),
296            Some(cow) => {
297                let mut writer = cow.into_owned();
298                writer.extend(iterator);
299                writer.into()
300            }
301        }
302    }
303}
304
305impl<'a, D: HeapStr, B: HeapStr> FromIterator<&'a KString<D>> for KString<B> {
306    fn from_iter<I: IntoIterator<Item = &'a KString<D>>>(iter: I) -> KString<B> {
307        let mut writer = KStringWriter::new();
308        writer.extend(iter);
309        writer.into()
310    }
311}
312
313impl<D: HeapStr, B: HeapStr> FromIterator<KString<D>> for KString<B> {
314    fn from_iter<I: IntoIterator<Item = KString<D>>>(iter: I) -> KString<B> {
315        let mut writer = KStringWriter::new();
316        writer.extend(iter);
317        writer.into()
318    }
319}
320
321impl<'a, 's, B: HeapStr, D: HeapStr> FromIterator<&'a KStringCow<'s, D>> for KString<B> {
322    fn from_iter<I: IntoIterator<Item = &'a KStringCow<'s, D>>>(iter: I) -> KString<B> {
323        let mut writer = KStringWriter::new();
324        writer.extend(iter);
325        writer.into()
326    }
327}
328
329impl<'s, B: HeapStr, D: HeapStr> FromIterator<KStringCow<'s, D>> for KString<B> {
330    fn from_iter<I: IntoIterator<Item = KStringCow<'s, D>>>(iter: I) -> KString<B> {
331        let mut writer = KStringWriter::new();
332        writer.extend(iter);
333        writer.into()
334    }
335}
336
337impl<'a, 's, B: HeapStr> FromIterator<&'a KStringRef<'s>> for KString<B> {
338    fn from_iter<I: IntoIterator<Item = &'a KStringRef<'s>>>(iter: I) -> KString<B> {
339        let mut writer = KStringWriter::new();
340        writer.extend(iter);
341        writer.into()
342    }
343}
344
345impl<'s, B: HeapStr> FromIterator<KStringRef<'s>> for KString<B> {
346    fn from_iter<I: IntoIterator<Item = KStringRef<'s>>>(iter: I) -> KString<B> {
347        let mut writer = KStringWriter::new();
348        writer.extend(iter);
349        writer.into()
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    #[test]
358    fn test_kstringwriter_new() {
359        let writer = KStringWriter::new();
360        assert!(matches!(writer, KStringWriter::Inline(_)));
361    }
362
363    #[test]
364    fn test_kstringwriter_push_char_inline() {
365        let mut writer = KStringWriter::new();
366        writer.push('a');
367        assert_eq!(writer.as_ref(), "a");
368    }
369
370    #[test]
371    fn test_kstringwriter_push_str_inline() {
372        let mut writer = KStringWriter::new();
373        writer.push_str("abc");
374        assert_eq!(writer.as_ref(), "abc");
375    }
376
377    #[test]
378    fn test_kstringwriter_push_char_to_owned() {
379        let mut writer = KStringWriter::new();
380        for _ in 0..=KString::MAX_INLINE_LEN {
381            writer.push('a');
382        }
383        assert!(matches!(writer, KStringWriter::Owned(_)));
384        assert_eq!(writer.as_ref(), "a".repeat(KString::MAX_INLINE_LEN + 1));
385    }
386
387    #[test]
388    fn test_kstringwriter_push_str_to_owned() {
389        let mut writer = KStringWriter::new();
390        for _ in 0..=KString::MAX_INLINE_LEN / 3 {
391            writer.push_str("abc");
392        }
393        assert!(matches!(writer, KStringWriter::Owned(_)));
394        assert_eq!(
395            writer.as_ref(),
396            "abc".repeat(KString::MAX_INLINE_LEN / 3 + 1)
397        );
398    }
399
400    #[test]
401    fn test_kstringwriter_reserve_inline() {
402        let mut writer = KStringWriter::new();
403        writer.reserve(KString::MAX_INLINE_LEN);
404        assert!(matches!(writer, KStringWriter::Inline(_)));
405
406        let mut writer = KStringWriter::new();
407        writer.push('a');
408        writer.reserve(KString::MAX_INLINE_LEN - 1);
409        assert!(matches!(writer, KStringWriter::Inline(_)));
410    }
411
412    #[test]
413    fn test_kstringwriter_reserve_to_owned() {
414        let mut writer = KStringWriter::new();
415        writer.reserve(KString::MAX_INLINE_LEN + 1);
416        assert!(matches!(writer, KStringWriter::Owned(_)));
417
418        let mut writer = KStringWriter::new();
419        writer.push('a');
420        writer.reserve(KString::MAX_INLINE_LEN);
421        assert!(matches!(writer, KStringWriter::Owned(_)));
422    }
423
424    #[test]
425    fn test_kstringwriter_from_writer_inline() {
426        let mut writer = KStringWriter::new();
427        writer.push_str("abc");
428        let kstring: KString = writer.into();
429        assert_eq!(&kstring, "abc");
430    }
431
432    #[test]
433    fn test_kstringwriter_from_writer_owned() {
434        let mut writer = KStringWriter::new();
435        for _ in 0..=KString::MAX_INLINE_LEN / 3 {
436            writer.push_str("abc");
437        }
438        let kstring: KString = writer.into();
439        assert_eq!(&kstring, &"abc".repeat(KString::MAX_INLINE_LEN / 3 + 1));
440    }
441
442    #[test]
443    fn test_kstringwriter_extend_char() {
444        let mut writer = KStringWriter::new();
445        writer.extend("hello".chars());
446        assert_eq!(writer.as_ref(), "hello");
447    }
448
449    #[test]
450    fn test_kstringwriter_extend_str() {
451        let mut writer = KStringWriter::new();
452        writer.extend(["he", "ll", "o"]);
453        assert_eq!(writer.as_ref(), "hello");
454    }
455}