devela/text/fmt/
buf.rs

1// devela::text::fmt::buf
2//
3//! Non-allocating formatting backed by a buffer.
4//
5
6use crate::{iif, FmtResult, FmtWrite, _core::cmp::min};
7crate::_use! {compat::from_utf8}
8
9/// Returns a formatted [`str`] slice backed by a buffer, non-allocating.
10///
11/// Underneath it calls [`Fmt::format_buf`][crate::Fmt::format_buf] and [`format_args!`].
12///
13/// See also the allocating [`format!`] macro.
14///
15/// [`format!`]: crate::format
16///
17/// # Example
18/// ```
19/// # use devela::format_buf;
20/// let mut buf = [0u8; 64];
21/// let s = format_buf![&mut buf, "Test: {} {}", "foo", 42];
22/// assert_eq!(Ok("Test: foo 42"), s);
23/// ```
24/// # Features
25/// Makes use of the `unsafe_str` feature if enabled.
26#[macro_export]
27#[cfg_attr(cargo_primary_package, doc(hidden))]
28macro_rules! format_buf {
29    ($buf:expr, $($arg:tt)*) => {
30        $crate::Fmt::format_buf($buf, $crate::format_args![$($arg)*])
31    };
32}
33#[doc(inline)]
34pub use format_buf;
35
36/// A helper type that writes formatted text into a fixed byte buffer.
37#[derive(Debug)]
38pub(super) struct WriteTo<'a> {
39    buf: &'a mut [u8],
40    /// The number of bytes actually written.
41    len: usize,
42    /// Set to true if any call to write_str did not write the complete input.
43    pub(super) truncated: bool,
44}
45impl<'a> WriteTo<'a> {
46    pub(super) const fn new(buf: &'a mut [u8]) -> Self {
47        WriteTo { buf, len: 0, truncated: false }
48    }
49
50    /// Returns the written bytes as a valid UTF‑8 string.
51    ///
52    /// If the final write ended in the middle of a multi‑byte codepoint,
53    /// only the valid prefix is returned.
54    pub(super) fn as_str(self) -> &'a str {
55        match from_utf8(&self.buf[..self.len]) {
56            Ok(valid_str) => valid_str,
57            Err(e) => {
58                let valid_len = e.valid_up_to();
59                #[cfg(any(feature = "safe_text", not(feature = "unsafe_str")))]
60                {
61                    from_utf8(&self.buf[..valid_len]).unwrap()
62                }
63                #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
64                {
65                    // SAFETY: we only convert the confirmed valid utf-8 length
66                    unsafe { ::core::str::from_utf8_unchecked(&self.buf[..valid_len]) }
67                }
68            }
69        }
70    }
71}
72impl FmtWrite for WriteTo<'_> {
73    fn write_str(&mut self, s: &str) -> FmtResult<()> {
74        let available = self.buf.len().saturating_sub(self.len);
75        let s_bytes = s.as_bytes();
76        let n = min(s_bytes.len(), available);
77        iif![n > 0; self.buf[self.len..self.len + n].copy_from_slice(&s_bytes[..n])];
78        iif![n < s_bytes.len(); self.truncated = true];
79        self.len += n;
80        Ok(())
81    }
82}